All articles
i18n strategy

Moving from English-First to Global-First: A Practical Playbook for Product Teams

Abstract illustration of multiple language directions expanding outward from a central point

Most SaaS products are English-first by accident. The founding team writes in English, the codebase starts in English, and by the time the product is ready to expand into other markets, English-first patterns are baked into the architecture, the sprint ritual, and the relationship with translators. Going global-first is not a single decision -- it is a series of structural changes that need to happen in the codebase, the sprint process, and the way the team thinks about localization work.

This is a practical guide to making those changes without stopping product development.

What English-First Actually Means

English-first manifests in predictable ways:

  • Hardcoded English strings scattered through JSX, Python template literals, and email templates -- not in locale files.
  • UI layouts designed for English text length with no tested behavior for German (30-40% longer on average), Finnish (often longer still), or CJK (shorter in character count but different wrapping behavior).
  • Date, number, and currency formatting that uses `en-US` locale as default without a locale-aware formatting layer.
  • A translation process that starts after features are "done" -- meaning English ships first and other languages follow whenever the localization cycle completes.
  • Sprint planning that counts localization as a separate track, not as part of the definition of done for a feature.

None of these patterns were chosen deliberately. They emerged because the team built what they knew, in the language they worked in, using the patterns that were fastest to ship. Going global-first requires making deliberate choices that change each pattern.

Step 1: Externalize All Strings

The codebase change is the foundation. Every user-visible string needs to be in a locale file and accessed through your i18n library's translation function. This is the well-understood part of i18n -- it is the step most teams take when they first add localization support.

The part teams often skip: auditing for strings that escaped the first externalization pass. Template literals in utility functions. Error messages in API clients. Strings in server-side rendered emails. Tooltip text added six months after the initial externalization pass. A global-first codebase has zero hard-coded user-visible strings outside locale files, not "mostly externalized."

The audit approach: use a linter rule that flags string literals in UI-rendering code. Configure it as a CI warning first, then promote to a blocking error once the initial pass is complete. This prevents regression without requiring manual audits.

Step 2: Design for Text Expansion

English is a compact language. German and Finnish frequently expand by 30-50% in UI strings. Russian and Polish also expand significantly. Arabic and Hebrew are RTL and may require layout adjustments.

Global-first design means testing layouts with expanded strings before the feature ships, not after localization reveals the problem. A practical convention: in Figma, add a German or Finnish text variant of each component to verify the layout holds. In code, add a pseudo-locale or "expanded" locale to your test suite that replaces English strings with padded equivalents (e.g., English string prefixed with `[---` and suffixed with `---]` plus 30% character padding). Run visual regression tests with the expanded locale.

CJK text behaves differently from expansion testing: Japanese and Chinese tend to be shorter in character count but have different line-breaking rules and minimum font size requirements for legibility. Test CJK separately from expansion testing.

Step 3: Add Locale-Aware Formatting

The Internationalization API (`Intl`) in modern JavaScript handles date, number, currency, list, and relative time formatting with locale-aware output. The transition from English-first to global-first means using `Intl.NumberFormat`, `Intl.DateTimeFormat`, and `Intl.RelativeTimeFormat` wherever the application formats these values for display, rather than using `toLocaleString()` with no locale argument (which defaults to the system locale, which is usually `en-US` on a developer's machine).

The three formats that bite teams most consistently:

  • Currency: "$49.00" renders as "$49.00" for a German user, where the expected format is "49,00 $" or "49,00 USD." Use `Intl.NumberFormat(locale, { style: 'currency', currency: currencyCode })`.
  • Dates: month/day/year order varies by locale. Use `Intl.DateTimeFormat(locale, options)` with explicit `day`, `month`, `year` options rather than any manual date formatting.
  • Number separators: 1,000.00 (en-US) vs. 1.000,00 (de-DE). Let `Intl` handle this, never format numbers with manual separator logic.

Step 4: Change the Definition of Done

The sprint ritual change is where English-first → global-first actually happens at the team level. As long as "done" means "shipped in English, localization to follow," you are shipping a two-tier product: English users get the current version, everyone else waits.

The global-first definition of done for a feature: all user-visible strings are in locale files, source strings are queued for translation, and either translations are complete or the translation debt is explicitly tracked as a follow-on task with a specific sprint target. The feature is not "done" until the localization path is clear.

In practice, this does not mean blocking English release on translation completion. It means translation work is initiated as part of the sprint, not after it, so the gap between English release and translated release is the translation cycle time, not the translation cycle time plus the time it takes someone to notice the strings need translation.

Step 5: Build the Translator Relationship Into the Sprint

The translator relationship changes when translation is part of the sprint rather than a follow-on task. Translators need context that they can only get from the team: what does this feature do, what is the intended tone, are there terms of art specific to this product that should be used consistently?

Global-first teams build translator briefing into the sprint cycle. Before strings are queued, a brief context document goes to the translation team: feature name, what it does, any terminology decisions, any strings where the context is non-obvious. This is 30 minutes of work per sprint per translator team and reduces re-translation requests significantly.

The translator is not a downstream vendor -- they are a team member who happens to be working in a different language. Treating the relationship that way, with the information sharing that implies, is the cultural shift that makes global-first sustainable rather than just structural.

The Transition Timeline

Going from English-first to global-first is not a single sprint. For a product in active development with an established codebase, a realistic timeline:

  • Sprint 1-2: Audit and externalize remaining hard-coded strings. Add linting rule for new hard-coded strings.
  • Sprint 3-4: Add locale-aware formatting across the codebase. Test with pseudo-locale.
  • Sprint 5-6: Update definition of done to include localization queue. Brief translator team on current product state.
  • Sprint 7+: New features built global-first from the start. Old features localized incrementally based on usage.

The structural work in the first six sprints creates the foundation. After that, global-first is the default mode of development, not a catch-up project. The German checkout is not two sprints behind the English checkout -- they ship together, because the pipeline that makes that possible is already running.

Localization that moves with your code.

Try Tonguesage free. Connect a repo and see your first sync in minutes.

Get started free