In the first few months of building Tonguesage, we ran without translation memory. The product was small, the string count was manageable, and TM felt like infrastructure we would add when we needed it. We had other things to ship. We would get to it.
We did not get to it until after the 47-string incident.
What Happened
We had three product areas that each used empty-state and error-message copy independently: the dashboard, the string diff view, and the translation queue. Each area was built by different team members at different times. Each had its own small set of empty states, error messages, and helper text.
When we pulled together the locale files for a round of translation across all three areas, we found 47 strings that were semantically identical or close enough that a translator would produce the same target text -- but written differently in English by different people. "No results found." versus "Nothing found." versus "No matching strings." All three meant the same thing to a user seeing an empty list, but they were three separate translation units in three separate files.
Across twelve languages, that is 141 translation units where it should have been 47. Roughly 100 translation units of waste -- and more importantly, three slightly different translations per language for what users experienced as the same UI state. In German, we ended up with "Keine Ergebnisse gefunden.", "Nichts gefunden.", and "Keine passenden Strings." All correct. All slightly inconsistent. All costing translation budget for something that should have been free.
Why We Rationalized Skipping TM
The rationalization was reasonable-sounding at the time. TM is most valuable at scale. We were a small team. The overhead of setting up and maintaining a TM database felt disproportionate. We were moving fast and string count was low enough that even without TM, the translation cost per sprint was manageable.
The flaw in that reasoning: TM is not just about cost at scale. It is about consistency from the beginning. The inconsistency cost we paid later was higher than the setup cost we avoided earlier. And the consistency problem was structural -- three product areas diverging independently produced the problem, and it would have continued producing the problem as we added more areas.
What We Would Do Differently
The correct time to set up translation memory is before you have enough strings to make consistency a problem -- not after. The setup cost for a basic TM in a modern TMS is not high. The ongoing maintenance cost is near zero for a product in the 5,000-string range. The benefit is that you catch semantic duplicates before they are translated, not after.
The specific changes we made after the 47-string incident:
Source-Level Deduplication Pass
Before any batch of strings goes to translation, a deduplication pass runs against the source locale (English). It identifies strings that are exact or near-exact matches by cosine similarity of the normalized text. Strings above a 90% similarity threshold are flagged for a human review: are these semantically equivalent? If yes, consolidate to one canonical string before translation. If no, they proceed independently.
This one step would have caught the 47-string problem before it was a problem. It takes about 20 minutes to set up a basic script for this on any modern language model or even a simple similarity library.
TM-First Queue Processing
When new strings enter the translation queue, the TM is consulted before any new translation job is created. If an exact match exists and the source text has not changed since the match was made, the TM match is applied directly. If a fuzzy match exists (85%+ similarity in our calibration), it is presented to the translator as a candidate rather than requiring a fresh translation from scratch.
The practical effect: the translator works faster, translation cost per sprint decreases, and the approved translation set grows as the TM accumulates coverage. The longer you run with TM, the higher your match rates become for the strings that are stable.
Semantic Grouping for UI Copy
Empty states, error messages, and helper text tend to cluster semantically. Instead of treating them as independent strings, we now group them by semantic function during the source authoring phase. "No results found," "No matching items," and "No results yet" all live in the same semantic bucket and are written with consistency in mind before they go to translation, not after.
This is a content strategy choice, not a technical one. It requires the person writing UI copy to know what strings are already in the system and use existing language when it fits rather than writing fresh copy every time. It is a lightweight discipline but it has a measurable effect on TM hit rates.
The Lesson
Translation memory is not overhead. It is consistency infrastructure. For a small team shipping in multiple languages, the sooner you have it, the lower the cleanup cost when the product grows. The 47-string incident cost us more in translator time and localization QA than the TM setup would have cost. By a significant margin.
Set up TM before your first multi-language translation batch. Source-level deduplication before you send strings to translators. TM-first queue processing from day one. The setup is an afternoon of work. The alternative is discovering the problem six months later and spending two weeks reconciling inconsistent translations across twelve languages.