SyncTrans translates live speech, and live speech about money tends to be exact: a price, a quantity, a discount. This post is about a class of bug where the translation is fluent, confident, and wrong in the digit that matters — and the unglamorous fix we shipped for it in 1.0.0.
The discount that flips its own sign
Chinese and Japanese both have idiomatic ways to state a discount, and they count in opposite directions. Chinese counts what you pay: 七折 means you pay 70% of the list price, so it is 30% off. Japanese counts what comes off: the same offer is 3割引 — three tenths removed. Every Chinese speaker and every Japanese speaker learns their own convention early and never thinks about it again.
A translation model sees something else entirely: 七折 and 割引 co-occur constantly in discount contexts, and the digit 七 maps to 7. In our early testing, models would cheerfully render 七折 as 7割引 — which a Japanese listener hears as 70% off. A supplier saying “I can give you 30% off” comes out the other end as “I can give you 70% off.” In a price conversation, that is the difference between a deal and a very expensive misunderstanding, and both sides walk away sure they heard the same number.
The same trap, one order of magnitude up
Discounts are the sharpest case, but the underlying shape shows up in plain numbers too. English counts large numbers in thousands; Chinese counts them in 万 (ten thousand) and 億 (hundred million). 12万 is 120,000. 1.2億 is 120,000,000. A model that carries the digits across without re-anchoring the unit produces “12,000” for 12万 — fluent, grammatical, and off by a factor of ten. Spoken numbers make it worse, because recognition hands the translator a stream of words, and the unit that disambiguates the magnitude can arrive a full breath after the digits.
Why the model falls for it
It took us a while to internalize why this kept happening across different models. A discount is a number with a direction, and the direction lives in the number convention, not in any word the model can carry across. There is no vocabulary entry that maps 折 onto “off” correctly, because 折 was never about what comes off — it was always about what stays. The model reproduces the surface pattern it has seen ten thousand times and has no internal ledger that notices the value flipped.
Crucially, this is a convention problem, and we verified that it is not a capacity problem. We built a small regression corpus of discount and large-number phrases in both directions and ran it against every model tier SyncTrans ships, on both on-device stacks and through the cloud engine. The inversion appeared at every size, on every stack. Bigger models phrased the wrong number more fluently. Once the measurements looked like that, the conclusion wrote itself: waiting for a better model would not fix this, so the fix had to live outside the model.
A rule layer behind the model
The fix in 1.0.0 is a deterministic post-processing layer that sits behind the translator and watches for numeric expressions with a convention attached. When a discount phrase parses unambiguously, the layer stops treating it as language and treats it as arithmetic: 七折 is converted to its explicit value — pay 70%, i.e. 30% off — and then re-expressed in the target language's own convention. The 折 is arithmetic, so we do it with arithmetic. Myriad-grouped numbers get the same treatment: the magnitude is computed from the unit, then rendered in the target language's grouping.
The appeal of this layer is that it is boring. Each rule is a small, unit-testable function with a fixed answer, pinned by the same corpus that proved the bug. When a rule fires, we can say exactly why; when a model improvises, we cannot. For the one class of phrases where a fluent error costs someone money, we wanted a component that gives the same answer every time and can be audited line by line.
What the hybrid costs us
We went in with our eyes open about the tradeoffs, and they are real. A rule layer is only as good as its coverage, so the first discipline is restraint: a rule fires only when the phrase parses cleanly, and anything ambiguous defers to the model. A rule that guesses would corrupt output the model would have gotten right, which is the one outcome worse than the original bug. The second cost is maintenance — conventions are per language pair, the corpus has to grow with each pair we take seriously, and every rule needs measurements on both sides. And the model still does everything else: the prose, the tone, the context. The rules are a backstop for a narrow, measurable class of failures, kept deliberately too small to become a second translator.
Where this stands
The number rules shipped in SyncTrans 1.0.0 and the corpus runs in our regression suite, so a future model upgrade has to keep passing it. The work is not finished: hardening around large-number boundaries — mixed units, magnitudes spoken in loose everyday form — is still ongoing, and we expect the corpus to keep growing as real conversations teach us new ways to be wrong. If a number ever comes out of SyncTrans looking inverted or shrunk, tell us — that report goes straight into the corpus.
For the wider story of what else is in the 1.0.0 release, see The road to SyncTrans 1.0.