How to Translate ASS/SSA Subtitles Without Breaking Styles, Positions, or Karaoke
Translate only the visible dialogue in an .ass file, then check timing, tags, karaoke, and fonts. With a sample file, a free checker, and a 9-run local LLM test.

An ASS or SSA subtitle file is a small program: a header, a table of styles, and a list of timed events whose last field mixes dialogue with formatting code. To translate it, change only the readable words in that last field, leave everything else byte-for-byte alone, and then check three things that no translation tool fixes for you: the fonts, the spacing codes, and the karaoke lines.
We translated a 14-event sample into Simplified Chinese nine times with a local model, using three different prompts, and checked every result with a script we wrote. The timing, positions, colors, comments, and drawings survived in all nine. What broke were the parts of the file that assume English: in all nine, the style fonts had no Chinese characters, so the file would rely on the player to substitute a font, and a hard space from the English line became a visible gap in the Chinese. The karaoke line came back with its timing pointed at the wrong words in every run, and in four runs some syllables highlighted nothing at all.
Quick Answer: What to Translate and What to Leave Alone
| Part of the file | Example from our sample | Translate it? |
|---|---|---|
[Script Info] header | PlayResX: 1920, WrapStyle: 0 | No. Positions are calculated from these values. |
[V4+ Styles] | Style: Default,Trebuchet MS,64,... | No, except the font name, which you may need to change on purpose |
| Event fields 1–9 | 0,0:00:01.00,0:00:03.20,Default,Jun,0,0,0, | No. These are layer, timing, style, speaker, margins, and effect. |
| Override blocks | {\i1}, {\an8\pos(960,120)}, {\k25} | No |
| Drawings | {\p1}m 0 0 l 200 0 200 60 0 60{\p0} | No. This is a vector shape, not text. |
Comment: events | Comment: ...,Earlier draft: Try the stacks. | Optional. Comments never appear on screen. |
| Brace notes | {TL note: "stacks" means the closed shelves} | Optional. Players ignore them. |
| Visible dialogue | Did you find the second volume? | Yes |
| Line codes | \N, \n, \h | Keep \N. Decide case by case for \n and \h. |
The rest of this guide explains why each row matters and how to check the result.
How an ASS Line Is Built
Every line in the [Events] section follows the format declared just above it:
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:03.40,0:00:06.10,Default,Mara,0,0,0,,{\i1}I thought it was{\i0} sold out.
Only the tenth field, Text, holds anything a viewer reads. The libass project's ASS file format guide notes that this last field "is allowed to contain commas", which is why a careless split on every comma breaks a line like Well, if it's there, then, we go now. A safe tool splits on the first nine commas only.
Inside Text, anything in curly braces is an override block. Aegisub's override tag reference defines the codes you will meet most often:
\Nforces a line break in every wrap mode.\nbreaks the line only in wrap mode 2 and is otherwise a space.\his a non-breaking space.\an8sets alignment using the numeric-keypad layout, so 8 is top center.\pos(960,120)places the line at script coordinates.\k25is a karaoke syllable; durations are in centiseconds, so 25 means a quarter of a second.- After
\p1, the text "will then be interpreted as drawing instructions", so a translator who "translates"m 0 0 l 200 0destroys a shape. - Unrecognized text inside braces "is silently ignored", which is why fansub files often carry translator notes in braces.
Lines that start with Comment: use the same fields, but according to the libass guide "nothing will be displayed on screen."
What We Tested
We wrote sample-source.ass, a 14-event scene set in a bookshop, with one example of each feature above: italics, a hard break, commas in dialogue, a brace note, a comment line, a positioned sign, a vector box, a fade, a hard space, a color change with \r, a soft break, a scrolling Banner effect, and a six-syllable karaoke line.
Then we asked a local model to translate it into Simplified Chinese in three ways:
- Whole file. Paste the entire
.assfile and ask for the translated file back. - Text only. Send only the
Textfield of each dialogue line as JSON, with the tags inline, and the instruction to keep every{...}block and every\N,\n, and\h. - Protected. The same, but replace every brace block and line code with a numbered placeholder first, and put the originals back afterwards.
In the second and third conditions the script never sends the vector drawing or the comment line to the model, so those lines could only be damaged in the whole-file condition.
The model was Qwen 3.6 27B (Q4_K_M) in LM Studio 0.4.8 on an Apple M4 Max with 64 GB of memory, with thinking turned off and temperature 0.3. We ran each condition three times, nine runs in total. The first attempt at the third run ended when the local server dropped the connection, so we restarted it and ran the third round again. The script is run_llm_ass_test.py, and all nine outputs are published unedited alongside it; the README lists every file. If you have never set up a local model, our local LLM translation guide covers the settings.
This was a small test of one model, one language pair, and one sample. It shows which problems happen and how to catch them. It does not rank tools or prompts.
Result: The Machinery Survived; the English Assumptions Did Not
We checked every output with check_ass_translation.py. The full output is in check-results.txt, and results.csv has one row per run, including problems the script cannot see.
| Check | Whole file | Text only | Protected |
|---|---|---|---|
| Timing, styles, and fields 1–9 unchanged | 3 of 3 | 3 of 3 | 3 of 3 |
| Override tags unchanged | 3 of 3 | 2 of 3 | 3 of 3 |
| Drawing and comment lines untouched | 3 of 3 | 3 of 3 | 3 of 3 |
| Style fonts able to draw the translation | 0 of 3 | 0 of 3 | 0 of 3 |
\h and \n left as gaps in Chinese text | 3 of 3 | 3 of 3 | 3 of 3 |
| All karaoke tags kept, each with text | 3 of 3 | 0 of 3 | 1 of 3 |
Two results surprised us. Pasting the whole file kept the structure intact in all three runs, though in two of them it also translated the Title line in the header. And the text-only prompt, which sounds safer, produced the only broken tags. In one run it returned {\k25}翻{\k30}页,{\k45}开{\k60}灯 for a line with six karaoke tags. The last two tags and 1.20 seconds of highlight timing were gone, so the highlight would end well before the line. The same run turned Room\h12 is upstairs. into 12\h12号房间在楼上。, with the room number twice.
So the useful lesson is not "use prompt X." It is that the structural damage you fear is easy to detect, and the damage that actually got through in all nine runs sits in three places the tool pages for ASS translation do not discuss.
1. The fonts cannot draw the new language
The sample's styles use Trebuchet MS for dialogue and Georgia for signs, which is normal for an English release. Neither font contains Chinese characters. When we pointed the checker's glyph test at the two font files, all 12 translated text events in every run failed:
ERROR event 1: font 'Trebuchet MS' has no glyph for 9 characters, e.g. 了二你到卷吗找第?
ERROR event 7: font 'Georgia' has no glyph for 10 characters, e.g. 书人仅作员工库架闭限
A player may substitute some other installed font, so the subtitle might still appear, but the release no longer controls what it looks like, and a viewer whose system lacks a suitable font may see empty boxes. The fix is a deliberate edit to the Fontname field of each style. When we changed all three styles in one output to Hiragino Sans GB, a Chinese font that ships with macOS, and ran the glyph test against that font, the glyph errors went to zero.
Choose a font your viewers will actually have, or ship it with the video. The libass guide says releases should attach every font they use, preferably to the MKV container. Since the target-language font is new, attaching it is now part of the translation job.
2. Spacing codes carry English habits
Room\h12 glues "Room" to "12" so they never split across lines. In Chinese the words are not separated by spaces, but every run kept the \h somewhere: five runs placed it between 12号房间 and 在楼上, three put it before the number, and one put it between two copies of the number. Either way, the viewer sees a stray space. Wait\nhere. had the same problem. With WrapStyle: 0, the \n renders as a space, so 在\n这里等 shows a gap in the middle of a three-word sentence.
The structure is fine here; the language is not. For a target language without spaces between words, remove \h and \n from the translated text. Keep \N only where you want a forced break, and place it at a natural point in the target sentence. The checker reports changed \h and \n counts as warnings rather than errors for this reason, and it warns when one survives inside Chinese, Japanese, or Korean text.
3. Karaoke timing belongs to the original words
The karaoke line {\k25}Turn {\k30}the {\k45}page, {\k60}turn {\k40}the {\k80}light times each English syllable to the song. A translation cannot keep that timing meaningfully, because the Chinese words do not fall on the same beats. Our runs show the different ways it goes wrong:
- In four runs all six tags were kept, each on a different word than before. One of them ended with
{\k40}打{\k80}开, "open", and dropped "light" from the line entirely. - In four runs a syllable highlighted nothing, such as
打开{\k40}{\k80}灯(0.40 seconds of highlight on an empty syllable). In two of those, a 0.45-second syllable also highlighted only a comma. - In one run two tags disappeared, as described above.
The checker flags a karaoke line translated in place and any syllable left with no text. The better fix is editorial: do not translate karaoke in place. Keep the original timed line for singing along, and add the translation as a separate event: copy the karaoke event, give it its own style (for example, smaller and placed at the bottom), remove the \k tags, and translate the text. Re-time it syllable by syllable only if you are prepared to time it to the audio yourself.
A Workflow That Holds Up
-
Make the source readable first. If the file shows garbled characters, fix the encoding before anything else. Our encoding guide uses SRT examples, but the same byte-level steps work for an
.assfile and keep the original bytes safe. The checker reads only UTF-8 and UTF-16. -
Decide the scope. List the styles and decide which ones are dialogue, which are on-screen signs, and which are karaoke. Signs usually need translation; karaoke usually needs a separate line; comments and brace notes are yours to decide.
-
Translate only the
Textfield. Use a tool that parses ASS rather than treating it as plain text, or protect the tags yourself as in our third condition. Keep the source file next to the output. -
Run the structural check.
python3 check_ass_translation.py source.ass translated.assIt compares event count, event types, fields 1–9, style values, override blocks, drawings, comment lines,
\N, karaoke syllables and durations, and lines that came back empty or unchanged. It does not judge translation quality. -
Fix the fonts on purpose. Change
Fontnamein each style that now carries target-language text, then run the glyph test against the font file you will ship:pip install fonttools python3 check_ass_translation.py source.ass translated.ass --font "Noto Sans SC=/path/to/NotoSansSC-Regular.otf"A changed font name appears as a warning, so you can confirm every change was intended.
-
Clean up spacing codes and karaoke as described above.
-
Watch it with the video. Open the file in Aegisub or your player. Check that signs still fit the shapes drawn behind them, because a
\pbox keeps its English size while a translated sign can be much longer or shorter. Check that lines are not too long for their time on screen. Neither problem is visible in the file itself.
Where BookTranslator Fits
BookTranslator's Subtitle Translator accepts SRT, VTT, ASS, and SSA files. According to the product page, it keeps timestamps and cue order and keeps supported ASS and SSA styling attached to each subtitle entry, so you do not have to extract and reinsert the text yourself. It translates dialogue with the surrounding lines as context, which helps with names and running jokes across a whole episode.
We did not run this sample through BookTranslator for this article. Whatever tool you use, run steps 4 to 7 on the result. A style that keeps its English font is still a style that cannot draw Chinese, and karaoke translated in place still needs an editorial decision. For the rest of the subtitle workflow, see our AI subtitle translation guide, our comparison of subtitle translation software, and, if your translation is going to YouTube, the YouTube subtitle workflow.
When to Convert to SRT Instead
Converting ASS to SRT before translating removes the styles, positions, drawings, and karaoke, which also removes most of the problems above. That is a reasonable trade if your destination only shows plain subtitles, or if the styling was never important to the release. It is the wrong trade for anime and music releases where signs and karaoke are part of the work. Decide based on where the file will be shown, not on which format is easier to translate.
Get the Test Files
Everything from this test is free to reuse:
- sample-source.ass: the 14-event English sample;
- check_ass_translation.py: the checker (Python standard library; the
--fonttest needs fontTools); - run_llm_ass_test.py: the three prompt conditions for any OpenAI-compatible local server;
- results.csv and check-results.txt: per-run results;
- README.txt: the file list and test settings, with the nine unedited outputs in the
outputsfolder.
Схожі публікації




