As a non-English speaker working with non-English customers, I’ve seen it countless times: developers default to English when naming things in code, even when our customers don’t speak a word of it. VacationRequest, OrderStatus, EmployeeHandler. It feels clean, universal, professional. But what if this habit is costing you clarity and introducing subtle bugs you never see coming? Let’s start with a bold but simple idea:
Write business logic in the language of your customer.
Why? Because the biggest source of bugs in modern software is probably misunderstanding business requirements. Misinterpretation leads to misimplementation. Suddenly, you’ve got workflows that technically work but don’t match what is actually needed. But where exactly do these problems start?
There are two layers of precision at play here. Natural language is the language your stakeholders speak (German, French, Spanish). Domain vocabulary refers to the specific terms that carry precise meaning in your business context (Urlaubsantrag, Genehmiger, Stornierung). When your German customer says Urlaubsantrag, they’re using both: a German word that also carries specific domain meaning. This post is about preserving both layers in your code.
Real-World Friction
Imagine you’re building a vacation management app for a German company. You maybe reach for terms like vacationDays, holidayRequest or isOnLeave. Seems reasonable, right? But something gets lost in translation. In German, Urlaub specifically means paid employee time off (the days you request from your employer). Ferien refers to school holidays or seasonal breaks. Feiertag is a public holiday like Christmas. These are distinct concepts with different rules and workflows. But when you translate them all to English, they collapse into the ambiguous blob of “holiday,” “vacation,” or “time off.” Which one did you mean when you named that variable holidayRequest? The precision that existed in the customer’s language has vanished.
When your codebase uses a different language than your customer speaks, you’re constantly converting terms in your head while trying to keep the semantic meaning intact. This back-and-forth translation creates unnecessary mental overhead. What makes it particularly insidious is that you often don’t notice the misalignments until they become bugs. The ambiguity was always there, hidden by the translation layer.
Here’s how this translation layer forms in practice. The customer describes their needs using precise domain terms: Urlaubsantrag, Mitarbeiter, Genehmiger. But as a developer, you translate these into what you think are equivalent English terms: VacationRequest, Employee, Approver. This creates a translation layer sitting between every customer conversation and your codebase:
Every time you discuss features, review requirements, or debug issues, you’re mentally converting between two vocabularies. That conversion step is where misunderstandings hide. Imagine you’re naming a class in this context. The wrong English term introduces ambiguity, both for you and your client:
class VacationRequest {
employeeId: number;
startDate: Date;
endDate: Date;
reason?: string;
}
A German stakeholder might ask: “Vacation? Meinen Sie Urlaub, Sonderurlaub oder Feiertag?” Now you have to explain, every time: “Urlaub. Not Sonderurlaub. Not Feiertag.”
Compare this to using the precise domain term directly:
class Urlaubsantrag {
mitarbeiterId: number;
startDatum: Date;
endDatum: Date;
grund?: string;
}
By naming your domain model Urlaubsantrag instead of VacationRequest, you eliminate that translation layer entirely:
With this approach, Urlaubsantrag in the code is the same Urlaubsantrag from customer conversations. Not an approximation, not a translation — the exact same concept. When a stakeholder says “Der Urlaubsantrag braucht noch eine Genehmigung”, you can immediately locate Urlaubsantrag and Genehmigung in your codebase without mental gymnastics.
The idea isn’t new. Eric Evans calls it “Ubiquitous Language”1 in his work on Domain-Driven Design. It’s about building a shared vocabulary between developers and domain experts that runs through everything from conversations to code. When customer and code speak the same words, there’s nowhere for misunderstandings to hide.
The important part here is that Ubiquitous Language does not stop at the semantic meaning but also includes the exact terms that are used in the customer’s language. Translating loses this precision.
But What About Technical Terms?
Here’s where things get nuanced. Not every part of your code should be translated. Technical constructs like request, response, counter, or framework-specific elements should remain in English. These are universal concepts shared across the global developer community. Translating them would disconnect you from documentation, Stack Overflow answers, and every tutorial that uses standard English terminology. Think of your codebase as having two distinct vocabularies that work together:
- Business logic: Use the customer’s language
- Technical infrastructure: Stick to English
For example, trying to translate APIClient into SchnittstellenKlient would be confusing at best, actively harmful at worst. Nobody searches for “SchnittstellenKlient best practices”. You’d be fighting the entire ecosystem. So where do you draw the line?
Never translate:
- Framework and library code (even if you’re extending it)
- Technical infrastructure (caching, logging, HTTP clients, database connections)
- Third-party integrations and API clients
- Build scripts, CI/CD configuration, deployment code
- Standard programming constructs (
if,for,map,filter, obviously!)
Usually keep in English:
- Utility functions that aren’t domain-specific (
formatDate,retry,debounce) - Technical abstractions (
Repository,Controller,Serviceas architectural patterns) - Cross-cutting concerns (authentication, authorization, observability)
The litmus test: Would this term appear in requirements documents, user stories, or stakeholder emails? If yes, it’s domain terminology, so use those exact terms. Would it only appear in technical documentation or developer discussions? Then it’s technical vocabulary, so stick with English. The boundary might feel fuzzy at first, but with practice it becomes intuitive.
Isn’t Mixing Languages in Code Ugly?
Initially, this might feel awkward. Seeing Urlaubsantrag next to HttpClient in the same file can trigger that “this looks wrong” instinct many developers have. We’re trained to value consistency, and mixed-language code violates that aesthetic sense.
But here’s the thing: this isn’t really about mixing languages. It’s about using precise terminology. You’re not “coding in German”—you’re using exact domain vocabulary that happens to be German. Urlaubsantrag isn’t a foreign word intruding on your codebase; it’s the precise term for the concept your stakeholders call “Urlaubsantrag”. When you think of it as domain-specific vocabulary rather than language mixing, the perceived ugliness often dissolves.
This also clarifies what happens with international teams, open-source projects, or acquired codebases. If stakeholders agreed on English domain terms, English becomes the customer’s language. The principle isn’t “use German” or “avoid English.” It’s: use the exact terms your stakeholders use. The enemy isn’t any particular language. It’s the translation gap between what stakeholders say and what code says.
And once you adopt this framing, you’ll notice your work is already multilingual. Your meetings, requirements documents, and stakeholder conversations constantly switch between technical English terms and domain concepts in the customer’s language. The code is simply reflecting how you already think and communicate about the system. Real-world clarity matters more than aesthetic purity—and homogeneity is not the same as maintainability.
Practical Guidance
By now it should be clear that variables, functions, classes, and other code elements should be named in the language your customer speaks to reflect the exact meaning of the underlying business concept. But what actually constitutes as a domain concept? Let’s look at some examples to get a better feeling.
Domain Models and Business Logic
This is the core principle. Your domain entities, value objects, aggregates, and services that implement business rules should use the customer’s exact terminology. Urlaubsantrag, Kundengruppe, Stornierungslogik. No translation, no approximation. This is where misunderstandings are most costly, so this is where precision matters most.
// Domain model - customer's language
class Urlaubsantrag {
private antragsteller: Mitarbeiter;
private genehmiger: Vorgesetzter;
genehmigen(): void { /* business logic */ }
ablehnen(grund: Ablehnungsgrund): void { /* business logic */ }
}
The tricky cases: What about concepts that straddle technical and domain-specific boundaries? Some methods genuinely are universal programming patterns that should stay English, even on domain objects:
class Warenkorb {
private artikel: Artikel[] = [];
private kundennummer: Kundennummer;
// Accessors: English pattern prefix + German domain term
getKundennummer(): Kundennummer { return this.kundennummer; }
setKundennummer(nr: Kundennummer): void { this.kundennummer = nr; }
// Domain method - customer language
leeren(): void {
this.artikel = [];
}
// Technical: serialization is infrastructure
toJSON(): object {
return {
kundennummer: this.kundennummer,
artikel: this.artikel.map(a => a.toJSON())
};
}
}
Accessor prefixes like get/set stay English because they’re universal programming patterns, but the property they access uses domain language, resulting in mixed names like getKundennummer() or setLieferadresse(). Similarly, toJSON(), fromJSON(), or clone() are infrastructure methods that nobody would translate to zuJSON() or klonen(). But domain methods like leeren() use customer language because stakeholders might say “Der Kunde will den Warenkorb leeren”.
This same pattern applies when technical infrastructure meets domain concepts. Let’s look at another example:
// Technical infrastructure - English
class EmailNotificationService {
async send(notification: EmailNotification): Promise<void> { /* ... */ }
}
// Domain concepts - customer's language
class UrlaubsantragGenehmigungEmailNotification implements EmailNotification {
subject(): string {
return `Ihr Urlaubsantrag wurde genehmigt`;
}
}
Notice the split: EmailNotificationService and EmailNotification are architectural patterns that could work for any application sending emails, regardless of domain. But UrlaubsantragGenehmigungEmailNotification is specific to your business domain.
You’ll find this boundary everywhere: strategies, services, factories, repositories — any pattern where a generic technical framework handles specific domain instances. The infrastructure stays English, the domain implementations use customer language.
Code Comments and Documentation
Comments are largely a matter of personal and team preference. Use whatever language feels most natural to explain your intent, but follow one critical rule: always use domain terminology when referring to domain concepts, even if the rest of the comment is in another language.
This means mixed-language comments are perfectly fine:
// Only allow 3 aktive Urlaubsanträge per Mitarbeiter at any time
if (aktiveAntraege.length >= 3) {
throw new MaxAntraegeUeberschrittenError();
}
// When a Urlaubsantrag exceeds 10 days, additional approval from Geschäftsführung is required
if (antrag.getDauer() > 10) {
benoetigeZusaetzlicheGenehmigung(Rolle.GESCHAEFTSFUEHRUNG);
}
Notice how each comment uses English as the primary language but keeps the domain terms intact: Urlaubsanträge, Mitarbeiter, Geschäftsführung. This works because the domain terms are precise identifiers for specific business concepts. You’re using exact domain vocabulary alongside natural explanation.
Exceptions and Error Messages
Technical exceptions stay English (HttpException, ValidationException, NotFoundException) since these are standard patterns recognized across codebases. But domain rule violations should use customer terminology:
// Technical exceptions - English
class ValidationException extends Error {}
// Domain exceptions - customer's language
class UrlaubskontingentUeberschrittenError extends Error {
constructor(public verfuegbareTage: number, public beantragteTage: number) {
super(`Urlaubskontingent überschritten: ${beantragteTage} Tage beantragt, nur ${verfuegbareTage} verfügbar`);
}
}
When you’re debugging at 2am and see UrlaubskontingentUeberschrittenError in your logs, you immediately know which business rule failed. Compare that to something generic like InsufficientBalanceError. Which balance? Bank account? Vacation days? Loyalty points? The domain-specific name eliminates ambiguity.
Log messages work similarly. Use domain terminology to quickly identify business concepts:
logger.error(`Urlaubsantrag ${id} abgelehnt: Jahreskontingent überschritten`);
logger.warn(`Bestellung ${id} kann nicht storniert werden: bereits versandt`);
These map directly to stakeholder conversations. When a bug report says “customers can’t cancel orders that were already shipped,” you can grep for bereits versandt and find exactly where that business rule is implemented.
Tests
If your domain logic uses customer language, your tests should follow suit. After all, tests are living documentation of your business rules:
describe('Urlaubsantrag', () => {
it('sollte Anträge ablehnen die das Jahreskontingent überschreiten', () => {
// Test that vacation requests exceeding annual allowance are rejected
});
it('sollte automatisch genehmigt werden wenn weniger als 5 Tage', () => {
// Test auto-approval for requests under 5 days
});
});
This creates living documentation that domain experts could actually read and understand. When a test fails, the description tells you exactly which business rule broke, in the exact same language stakeholders use.
Consistency Across the Stack
So far we’ve looked at where to apply customer terminology in code: domain models, comments, exceptions, tests. But your code doesn’t exist in isolation. It spans layers: frontend components, API endpoints, service methods, database columns. Think of all these layers as connected by an invisible chain, where consistent naming of domain concepts forms the links.
Domain terminology flows from stakeholder conversation through every layer of the stackWhen you stop naming things consistently, you create gaps in the chain. A VacationRequest in the frontend that maps to Urlaubsantrag in the service layer breaks the link. Suddenly, you need to remember which translation applies where. Suddenly, the clarity you built evaporates.
This is why consistent naming matters even in layers that stakeholders never see. Your database schema, your internal services, your background jobs: they all benefit from the same terminology. Not because stakeholders will read that code, but because you will.
Conclusion
Every time you translate a domain concept into English, you create a gap where misunderstandings can hide. VacationRequest looks clean until someone asks which kind of leave it represents. CustomerGroup seems obvious until you realize stakeholders actually distinguish between Kundengruppe, Geschäftspartner, and Kundensegment. These translation gaps don’t announce themselves. They surface weeks later as bugs, rework, and confused conversations.
Using your customer’s exact terminology closes that gap. When stakeholders say “Urlaubsantrag,” your code says Urlaubsantrag. No mental conversion, no ambiguity, no wondering what the English approximation really meant. Technical infrastructure stays in English because that’s universal developer vocabulary. But domain concepts? Those belong to your customer.
The result looks weird at first. German words next to English ones. But that mixed-language codebase isn’t a compromise - it reflects the bilingual reality of building software for non-English speakers. And more importantly, it forces precision. You can’t hide behind a vague translation when the exact domain term is staring back at you from the code.
Homogeneity is not the same as maintainability. Clarity is.