i18n decisions that affect localization cost

Most teams think about localization cost in terms of translation: how many words, how many languages, which provider. But a significant portion of the cost is already locked in before a single string reaches a translator. It gets decided in pull requests, architecture discussions, and key naming conventions made months or years earlier.
Some i18n decisions are cheap to make and expensive to undo. Others save money every time a new language is added. Understanding which is which helps you build a codebase that treats localization as infrastructure rather than recurring overhead.
Hardcoded strings
This one is foundational. When user-facing text is written directly into components rather than referenced through translation keys, every new language requires a developer to touch source code. There is no clean handoff to a translator.
// costs you money every time you add a language
<button>Save changes</button>
// costs almost nothing to scale
<button>{t("settings.save_changes")}</button>
The fix is straightforward, but the scope is what makes it expensive. A codebase with thousands of hardcoded strings across hundreds of components can take weeks to fully externalize. Once done, the translation pipeline works for every language you ever add. Skipping it means you pay developer time on every new locale.
If you are starting a new project, using translation keys from day one costs almost nothing. Retrofitting them later costs a lot. This is probably the single most expensive delay in localization planning, and we have written about why it is so hard to undo.
Vague or ambiguous key names
Translation is priced partly by complexity. Ambiguous strings require more back-and-forth with translators, more revision cycles, and more errors that only surface in QA. This is avoidable.
// ambiguous - what is "home"? a house? a nav button?
{ "home": "Home" }
// clear - no ambiguity for the translator
{ "nav.home_button": "Home" }
A key like button_1 tells a translator nothing. A key like checkout.place_order_button tells them exactly what they are translating and where it lives. Good key naming reduces translator questions, reduces QA cycles, and reduces the chance that German users see "Zuhause" (a house) where they should see "Startseite" (a navigation destination).
There is a full breakdown of translation key naming conventions and best practices worth reading before you establish naming conventions on a new project.
Duplicate strings without shared keys
When the same phrase appears in multiple places in the UI ("Cancel", "Save", "Loading..."), teams often create separate keys for each occurrence. The content is identical; the keys are not. Every duplicate key is a translation you pay for more than once.
// paying for "Cancel" three times
{
"modal.cancel": "Cancel",
"form.cancel_button": "Cancel",
"dialog.cancel_action": "Cancel"
}
// pay once, use everywhere
{
"common.cancel": "Cancel"
}
This adds up fast. A project with 300 unique strings but 500 keys due to duplication is paying for 200 translations that did not need to exist. It also creates consistency problems: if "Cancel" becomes "Dismiss" after a copy audit, you now have three keys to update instead of one.
Shared keys across common namespaces and even across multiple projects directly reduces translation volume and cost.
Pluralization done wrong
Pluralization handled with string concatenation or conditional logic instead of proper ICU message format is a trap that gets more expensive as you add languages.
// works in English, breaks in Polish, Arabic, Russian
const message = `You have ${count} item${count === 1 ? '' : 's'}`;
English has two plural forms. Polish has four. Arabic has six. Russian changes form depending on the last digit of the number. The string above produces incorrect output in dozens of languages, and fixing it retroactively means touching every string that uses this pattern.
Using ICU message format for pluralization from the start avoids this entirely. The translation file handles the language-specific rules; the code stays clean.
{
"cart.item_count": "{count, plural, one {You have # item} other {You have # items}}"
}
The cost of getting pluralization wrong is paid in QA bugs, translator confusion, and developer time spent fixing broken strings per language. It compounds with every language added.
Not splitting translations by namespace
A single flat translation file per language works for small projects. At scale, it creates two problems that cost money.
First, performance: loading a 200KB translation file on every page when the user only needs 10% of its contents is waste you will eventually have to refactor. Second, ownership: when billing strings, onboarding strings, and error messages all live in one file, every translation update touches the same artifact. It creates conflicts, slows reviewers down, and makes it harder to know what changed.
Splitting by namespace (one file per product area per language) reduces load time and makes translation ownership much cleaner. The billing team owns billing.json; they do not need to open onboarding.json to do their work.

This is also where lazy loading translations becomes relevant: with namespaces, you can load only what a given route actually needs, which matters both for performance and for reducing the surface area of what translators need to review per release.
Formatting dates, numbers, and currencies as strings
Hardcoding a date format or currency symbol as a translated string rather than using locale-aware formatting is a subtle but recurring cost.
// this is now a localization problem, not a formatting problem
t("price_label", { price: "$12.99" })
// the formatting belongs in code, not in translation files
new Intl.NumberFormat(locale, { style: 'currency', currency: 'USD' }).format(12.99)
When formatting is embedded in translation strings, translators have to handle it. That means translator instructions, edge cases, QA checks for whether € 12,99 or 12,99 € is correct per locale, and ongoing maintenance whenever the format needs to change. Using the browser's built-in Intl APIs for number and date formatting removes all of that from the translation workflow entirely.
No translation context
This is less about code architecture and more about workflow cost, but it belongs here because it is a decision teams make at the project setup stage.
A project where every translation key is just a bare string with no description, no screenshot, no usage hint will generate translator questions. Translators who do not know whether "Draft" refers to a document state, a beer, or an NFL pick will ask. Or they will guess wrong, and you will catch it in QA.
Adding context to translation keys before strings go to translation reduces revision cycles. For AI translation, context is even more important: a model that knows it is translating a navigation button for a fintech app aimed at younger users produces a measurably different (and better) result than one operating on a bare string.

Skipping pseudo-localization
Pseudo-localization replaces strings with accented, elongated variants before real translations exist:
"Save changes" → "[Ŝàvé çhàñgéš]"
Running this before you have any real translations surfaces two things cheaply: text that was hardcoded (it will not change), and layout that breaks under text expansion (the accented versions are longer and reveal containers with fixed widths).
Catching those issues during development costs nothing. Catching them after translations are done for 12 languages means fixing layouts, re-exporting files, re-importing, re-reviewing. The cost multiplies by the number of languages.
Not planning for localization from the start
There is a pattern across all of these decisions: the earlier you make them correctly, the less each new language costs. Poor i18n is not just a technical problem; it is a pricing model you build into your product from the start. The localization readiness checklist covers a lot of these questions if you want a structured way to audit where your project stands before you start adding languages.
And if you are still figuring out the broader strategy, including which markets to prioritize and how to measure what localization actually returns, the localization strategy guide is the right place to start.




