Skip to content

fix: keep non-English dictation in its own language during cleanup - #287

Open
melnikaite wants to merge 2 commits into
zachlatta:mainfrom
melnikaite:fix/keep-dictation-language
Open

fix: keep non-English dictation in its own language during cleanup#287
melnikaite wants to merge 2 commits into
zachlatta:mainfrom
melnikaite:fix/keep-dictation-language

Conversation

@melnikaite

@melnikaite melnikaite commented Aug 5, 2026

Copy link
Copy Markdown

The cleanup prompt says "No translation" and "Preserve the speaker's final intended meaning, tone, and language", but every example in it is written in English and models follow the examples over the rule. Dictating German at the default cleanup model comes back in English: "benenne user id in user unterstrich id um" returns as "rename user id to user_id". On smaller local models the whole transcript flips, greeting and all.

FreeFlow already knows the language being dictated — TranscriptionService sends it to the transcription API as the language field — and it already has the machinery for naming a language inside the cleanup prompt, in applyOutputLanguage. This adds the counterpart: when no output language is set, name the dictation language so cleanup preserves it instead of translating it.

Wording alone does not fix this, which is why the change rides the existing applyOutputLanguage shape rather than adding more prompt text. A generic rule ("write the output in the language of RAW_TRANSCRIPTION") had no measurable effect in three different placements, a clause scoped to the offending rule had none either, and adding a counterpart example in a different non-English language did not transfer to a third language. Naming the language explicitly is what works.

Auto-detect and English are skipped. There is nothing to name in the first case, and in the second the prompt's own examples are already English and English dictation never drifted in any measurement, so English-only users get a byte-identical request.

Measured at temperature 0 with 5 repeats, scoring the output language separately from the rule each case also exercises, because a model that cannot join "user underscore id" into "user_id" otherwise reads as a translation failure:

openai/gpt-oss-20b (default cleanup model)
German developer instruction 2/5 -> 4/5 kept in German
Russian 5/5 -> 5/5, never drifted
meta-llama/llama-3.1-8b-instruct (two runs pooled; see note)
Russian greeting 3/10 -> 10/10
Russian developer instruction 3/9 -> 9/9
German developer instruction 4/5 -> 4/5, already mostly fine
qwen/qwen3.6-27b (default fallback and context model)
unaffected, 5/5 -> 5/5 on every case
Qwen3-VL-8B-Instruct via a local OpenAI-compatible server
Russian and German 0/3 -> 3/3

gpt-oss-20b improves rather than fully recovers on German, 4/5 instead of 5/5. The effect is largest on small models, which is also where the app's support for a custom base URL puts it.

llama-3.1-8b is pooled across two runs because it was inconsistent between them even at temperature 0 — the Russian greeting scored 3/5 in one run and 0/5 in the other before the change, and 5/5 in both after it. The direction was the same every time; the absolute baseline was not, so quoting a single run would overstate the precision.

Deliberately mixed-language dictation stays mixed: "надо добавить retry на flaky тесты и запустить с флагом дэш дэш fix" keeps retry, flaky and --fix, which the prompt requires and a language directive could plausibly have flattened.

Summary by CodeRabbit

  • New Features

    • Improved transcript post-processing to preserve the selected dictation language when no output language is specified.
    • Added language-specific instructions to cleanup processing for more accurate transcription results.
  • Bug Fixes

    • Fixed transcript cleanup behavior so it no longer implicitly defaults to English when the dictation language differs.

The cleanup prompt says "No translation" and "Preserve the speaker's final
intended meaning, tone, and language", but every example in it is written in
English and models follow the examples over the rule. Dictating German at the
default cleanup model comes back in English: "benenne user id in user
unterstrich id um" returns as "rename user id to user_id". On smaller local
models the whole transcript flips, greeting and all.

FreeFlow already knows the language being dictated — TranscriptionService
sends it to the transcription API as the `language` field — and it already has
the machinery for naming a language inside the cleanup prompt, in
applyOutputLanguage. This adds the counterpart: when no output language is
set, name the dictation language so cleanup preserves it instead of
translating it.

Wording alone does not fix this, which is why the change rides the existing
applyOutputLanguage shape rather than adding more prompt text. A generic rule
("write the output in the language of RAW_TRANSCRIPTION") had no measurable
effect in three different placements, a clause scoped to the offending rule had
none either, and adding a counterpart example in a different non-English
language did not transfer to a third language. Naming the language explicitly
is what works.

Auto-detect and English are skipped. There is nothing to name in the first
case, and in the second the prompt's own examples are already English and
English dictation never drifted in any measurement, so English-only users get
a byte-identical request.

Measured at temperature 0 with 5 repeats, scoring the output language
separately from the rule each case also exercises, because a model that cannot
join "user underscore id" into "user_id" otherwise reads as a translation
failure:

  openai/gpt-oss-20b (default cleanup model)
    German developer instruction   2/5 -> 4/5 kept in German
    Russian                        5/5 -> 5/5, never drifted
  meta-llama/llama-3.1-8b-instruct   (two runs pooled; see note)
    Russian greeting               3/10 -> 10/10
    Russian developer instruction   3/9 -> 9/9
    German developer instruction    4/5 -> 4/5, already mostly fine
  qwen/qwen3.6-27b (default fallback and context model)
    unaffected, 5/5 -> 5/5 on every case
  Qwen3-VL-8B-Instruct via a local OpenAI-compatible server
    Russian and German             0/3 -> 3/3

gpt-oss-20b improves rather than fully recovers on German, 4/5 instead of 5/5.
The effect is largest on small models, which is also where the app's support
for a custom base URL puts it.

llama-3.1-8b is pooled across two runs because it was inconsistent between
them even at temperature 0 — the Russian greeting scored 3/5 in one run and
0/5 in the other before the change, and 5/5 in both after it. The direction was
the same every time; the absolute baseline was not, so quoting a single run
would overstate the precision.

Deliberately mixed-language dictation stays mixed: "надо добавить retry на
flaky тесты и запустить с флагом дэш дэш fix" keeps retry, flaky and --fix,
which the prompt requires and a language directive could plausibly have
flattened.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the size/s label Aug 5, 2026
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5ec06f09-4620-461c-b350-813b1fd6bd63

📥 Commits

Reviewing files that changed from the base of the PR and between 34a9737 and cb2fac1.

📒 Files selected for processing (2)
  • Sources/PostProcessingService.swift
  • Tests/AppContextServiceTests.swift
🚧 Files skipped from review as they are similar to previous changes (2)
  • Tests/AppContextServiceTests.swift
  • Sources/PostProcessingService.swift

📝 Walkthrough

Walkthrough

The post-processing flow now derives a dictation language, passes it into PostProcessingService, and adds a prompt rule when no output language is set. Tests and the test runner build were updated for the new flow.

Changes

Dictation language handling

Layer / File(s) Summary
Derive and pass dictation language
Sources/AppState.swift
AppState maps transcription language codes to English names and passes the result to transcript post-processing.
Propagate language through processing
Sources/PostProcessingService.swift
dictationLanguage flows through primary and fallback processing. A prompt directive preserves the dictation language when outputLanguage is empty.
Validate prompt behavior
Tests/AppContextServiceTests.swift, Makefile
Tests verify language naming and prompt preservation. The test runner compiles the updated service sources.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AppState
  participant PostProcessingService
  participant LanguageModel
  AppState->>PostProcessingService: processTranscript passes dictationLanguage
  PostProcessingService->>PostProcessingService: Propagate dictationLanguage through fallback processing
  PostProcessingService->>LanguageModel: Send prompt with dictation-language instruction
Loading

Possibly related PRs

  • zachlatta/freeflow#195: Both PRs modify the post-processing prompt logic in Sources/PostProcessingService.swift.
  • zachlatta/freeflow#251: Both PRs modify PostProcessingService to add prompt instructions and update related test/build wiring.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: preserving non-English dictation during cleanup.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/PostProcessingService.swift`:
- Around line 753-760: The dictation-language prompt currently forces all output
into one language, conflicting with mixed-language preservation. In
Sources/PostProcessingService.swift lines 753-760, update applyDictationLanguage
to instruct preserving spans spoken in other languages while retaining the
named-language guidance; in Tests/AppContextServiceTests.swift lines 79-98,
remove the single-language assertion and assert the mixed-language preservation
instruction instead.

In `@Tests/AppContextServiceTests.swift`:
- Around line 79-98: Update testDictationLanguageIsNamedInPrompt to remove the
assertion requiring “Output ONLY in Russian” and instead verify that Russian is
identified as the primary dictation language while mixed-language spans are
preserved in their original languages. Keep the existing prompt-prefix and
explicit-language assertions intact.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 856d1939-0d0e-4c3f-8b9f-cca68c95ed4f

📥 Commits

Reviewing files that changed from the base of the PR and between e63a232 and 34a9737.

📒 Files selected for processing (4)
  • Makefile
  • Sources/AppState.swift
  • Sources/PostProcessingService.swift
  • Tests/AppContextServiceTests.swift

Comment thread Sources/PostProcessingService.swift
Comment thread Tests/AppContextServiceTests.swift
The directive said "Output ONLY in <language>", which reads as a contradiction
of the prompt's own "Preserve mixed-language text exactly as mixed" even though
mixed dictation measured fine. Take the reviewer's wording: name the language as
the primary one and carve out foreign spans explicitly.

A/B'd the two wordings before switching, since "primarily" is a hedge and hedged
language rules had already failed at 0/3 earlier in this work. They tie:
Qwen3-VL-8B and Gemma 4 E4B both score 3/3 on all three drift cases and all six
mixed-language patterns under either wording, and openai/gpt-oss-20b scores 5/5
on every case under both. Equal reliability, one less contradiction in the
prompt.

The suggested test edit is not taken verbatim: it kept an assertion for
"The dictation is in Russian" alongside one for "The dictation is primarily in
Russian", and the second string does not contain the first, so the test would
have failed. Assertions now match the wording actually shipped and add the
mixed-language carve-out the review asked for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant