The Question Every Developer Asks
You're adding internationalization to your app. Your first Google search lands you on MDN's Intl API documentation. It looks promising: date formatting, number formatting, even relative time formatting, all built into the browser.
Why would you need anything else?
Here's the thing: the Intl API is fantastic for formatting, but internationalization isn't just formatting. It's content management, translation workflows, context for translators, version control, and automation.
This guide breaks down exactly what the browser can (and can't) do, and when you actually need a translation management system.
What the Browser Intl API Actually Does
The JavaScript Intl object provides locale-aware formatting. That's it. Not translations, not content management: formatting.
Intl API Capabilities
Number Formatting:
JavaScript1// US format 2new Intl.NumberFormat('en-US').format(1234567.89); 3// "1,234,567.89" 4 5// German format 6new Intl.NumberFormat('de-DE').format(1234567.89); 7// "1.234.567,89" 8 9// Currency 10new Intl.NumberFormat('ja-JP', { 11 style: 'currency', 12 currency: 'JPY' 13}).format(99000); 14// "¥99,000"
Date/Time Formatting:
JavaScript1const date = new Date('2026-01-15'); 2 3// US format 4new Intl.DateTimeFormat('en-US').format(date); 5// "1/15/2026" 6 7// UK format 8new Intl.DateTimeFormat('en-GB').format(date); 9// "15/01/2026" 10 11// Long format with time 12new Intl.DateTimeFormat('fr-FR', { 13 dateStyle: 'full', 14 timeStyle: 'short' 15}).format(date); 16// "mercredi 15 janvier 2026 à 00:00"
Relative Time Formatting:
JavaScript1const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' }); 2 3rtf.format(-1, 'day'); // "yesterday" 4rtf.format(2, 'week'); // "in 2 weeks" 5rtf.format(-3, 'month'); // "3 months ago"
Pluralization Rules:
JavaScript1const rules = new Intl.PluralRules('en-US'); 2rules.select(0); // "other" 3rules.select(1); // "one" 4rules.select(2); // "other" 5 6const rulesArabic = new Intl.PluralRules('ar-EG'); 7rulesArabic.select(0); // "zero" 8rulesArabic.select(1); // "one" 9rulesArabic.select(2); // "two" 10rulesArabic.select(5); // "few" 11rulesArabic.select(11); // "many"
List Formatting:
JavaScript1const list = new Intl.ListFormat('en', { style: 'long', type: 'conjunction' }); 2list.format(['apples', 'oranges', 'bananas']); 3// "apples, oranges, and bananas" 4 5const listFr = new Intl.ListFormat('fr', { style: 'long', type: 'conjunction' }); 6listFr.format(['pommes', 'oranges', 'bananes']); 7// "pommes, oranges et bananes"
That's powerful stuff. So what's missing?
What the Intl API Can't Do
Here's where developers get stuck. The Intl API doesn't handle:
1. Translation Content
The Intl API doesn't store or manage translated strings. You still need:
JavaScript1// ❌ This doesn't exist 2const t = Intl.translate('welcome.title', 'fr'); 3 4// ✅ You build this yourself 5const translations = { 6 en: { "welcome.title": "Welcome" }, 7 fr: { "welcome.title": "Bienvenue" }, 8 es: { "welcome.title": "Bienvenido" } 9};
You're responsible for:
- Loading translation files
- Organizing key structure
- Managing fallbacks
- Detecting user locale
- Switching languages dynamically
2. Complex Message Formatting
The Intl API can't handle variables in translations:
JavaScript1// What you need 2"Hello {name}, you have {count} new messages" 3 4// What Intl.MessageFormat can do (newer API) 5const msg = new Intl.MessageFormat( 6 "Hello {name}, you have {count, plural, one {# message} other {# messages}}", 7 "en" 8); 9msg.format({ name: "Sarah", count: 3 }); 10// "Hello Sarah, you have 3 messages"
But Intl.MessageFormat is:
- Still in Stage 1 proposal (not standardized)
- Only available in Chrome 117+ with a flag
- Not production-ready as of 2026
Most developers use ICU Message Format via libraries like @formatjs/intl or messageformat.
3. Translation Workflow
Zero support for:
- Sending strings to translators
- Tracking translation status
- Managing review cycles
- Version control for translations
- Collaboration between developers and linguists
4. Context for Translators
Translators need to see:
- Where the text appears in the UI
- Character limits
- Screenshots
- Usage notes
The Intl API has no concept of this.
5. Automation
No built-in support for:
- Extracting strings from code
- Auto-detecting missing translations
- CI/CD integration
- Pushing updates over-the-air
The Hybrid Approach: Best of Both Worlds
Most production apps use the Intl API for formatting and a library/TMS for translation management.
Example: React App with next-intl
TSX1import { useTranslations } from 'next-intl'; 2import { useFormatter } from 'next-intl'; 3 4function ProductCard({ product }) { 5 const t = useTranslations('products'); 6 const format = useFormatter(); 7 8 return ( 9 <div> 10 {/* Translation from TMS/files */} 11 <h2>{t('title', { name: product.name })}</h2> 12 13 {/* Formatting from Intl API */} 14 <p>{format.number(product.price, { style: 'currency', currency: 'USD' })}</p> 15 <time>{format.dateTime(product.releaseDate, { dateStyle: 'medium' })}</time> 16 </div> 17 ); 18}
The library (next-intl) handles:
- Loading translation files
- Managing the current locale
- Providing
t()function - Fallback logic
The Intl API (via format) handles:
- Number formatting
- Date formatting
- Currency symbols
- Locale-specific conventions
When Browser-Native is Enough
Use just the Intl API when:
1. Simple Formatting Needs
You only need dates, numbers, currencies formatted correctly:
JavaScript1// A dashboard showing metrics 2const dashboard = { 3 users: Intl.NumberFormat(locale).format(1543298), 4 revenue: Intl.NumberFormat(locale, { style: 'currency', currency }).format(42000), 5 lastUpdated: Intl.DateTimeFormat(locale, { dateStyle: 'medium' }).format(new Date()) 6};
2. No User-Facing Text
Your app has minimal UI text (e.g., a data visualization tool, internal admin panel).
3. Single Market Focus
You're building for one region and just want proper number/date formatting.
4. Learning/Prototyping
You're building a demo or learning internationalization concepts.
When You Need a Translation Management System
Use a TMS (like IntlPull, Lokalise, or Phrase) when:
1. Multiple Languages
You're targeting 3+ languages. Managing translation files manually becomes painful fast.
Pain points without TMS:
- Translators editing JSON/YAML files directly (breaks syntax)
- No visibility into what's translated vs missing
- No way to track "who translated what when"
- Merge conflicts in Git when multiple translators work
- No review workflow
With TMS:
- Web UI for translators (no code access needed)
- Automatic tracking of translation status
- Built-in review/approval workflow
- No merge conflicts (TMS handles sync)
2. Frequent Content Updates
You're adding features weekly. New strings appear constantly.
Manual workflow:
- Developer adds English strings
- Export strings to spreadsheet
- Email translators
- Wait for response
- Copy-paste back into JSON
- Deploy
TMS workflow:
- Developer pushes code with new strings
- TMS auto-detects missing translations
- Translators get notified
- Translations auto-sync back to code
- Deploy
IntlPull's CLI does this in one command:
Terminal1npx @intlpullhq/cli upload 2# Extracts new strings, uploads to platform 3# Translators see them immediately 4 5npx @intlpullhq/cli download 6# Downloads latest translations
3. Non-Technical Translators
Your translators aren't developers. They can't work with JSON files or Git.
What translators need:
- Visual context (where does this text appear?)
- Character limits
- Usage notes ("This appears on buttons, keep it short")
- Ability to see placeholders:
{name},{count} - Preview of their translations in the actual UI
TMSs provide translator-friendly interfaces. IntlPull shows live previews so translators see their changes in context.
4. Machine Translation + Human Review
You want to use AI for initial translations but have humans review.
Typical workflow:
- Auto-translate with ChatGPT/DeepL for all strings
- Humans review and fix errors
- Track what's been reviewed vs auto-generated
TMSs integrate with MT engines and track translation source (human vs machine).
5. Over-the-Air Updates
You want to update translations without redeploying your app.
Use case: You launch in Spanish, but users report a translation error. Without OTA:
- Fix translation in code
- Commit to Git
- CI/CD build
- Deploy to production
- Users get update (maybe days later)
With OTA (IntlPull's approach):
- Fix translation in web UI
- Click "Publish"
- Apps fetch new translations instantly
- No deployment needed
This is critical for mobile apps where app store review takes days.
6. Collaboration at Scale
Multiple people working on translations:
- Developers adding source content
- Translators translating
- Reviewers approving
- Marketing editing copy
Without a TMS, this is chaos. Everyone's overwriting each other in Git.
Decision Framework
| Scenario | Solution | Why |
|---|---|---|
| Internal tool, 1 language, date/number formatting | Intl API only | Simple, no translation management needed |
| Marketing site, 3-5 languages, static content | Intl API + JSON files | Translation files are manageable without TMS |
| SaaS product, 10+ languages, frequent updates | Intl API + TMS | Need workflow automation, collaboration, OTA |
| Mobile app, 5+ languages, app store delays | Intl API + TMS with OTA | Can't wait for app store approval for translation fixes |
| E-commerce, 20+ languages, product descriptions | Intl API + TMS | High volume of content, need MT + human review |
Common Misconceptions
"I can build my own TMS"
You can, but it'll take 6+ months to match what existing TMSs do.
What you'll build:
- Web UI for translators
- String extraction script
- Sync logic (code ↔ platform)
- Access control (who can edit what)
- Translation memory
- Search/filtering
- Version history
- API
- CLI
That's a product, not a weekend project. Unless translation management is your core competency, use an existing solution.
"TMSs are expensive"
Some are (Lokalise, Phrase charge $500+/month).
But indie-friendly options exist:
- IntlPull: Free tier, $29/month for small teams, OTA updates included
- Tolgee: Open-source option
- Crowdin: Free for open-source projects
"TMSs lock you in"
Most support standard formats (JSON, YAML, XLIFF). You can export and migrate.
IntlPull stores translations in your Git repo by default. You own your data.
The IntlPull Approach
IntlPull combines the best of both:
Uses Intl API for formatting:
JavaScript1// IntlPull SDKs wrap Intl API for formatting 2const { t, format } = useIntlPull(); 3 4// Translation management 5t('welcome.title'); 6 7// Formatting via Intl API 8format.number(1234.56, { style: 'currency', currency: 'USD' });
Adds what's missing:
- CLI for syncing translations:
npx @intlpullhq/cli sync --watch - Web UI for translators (visual context, screenshots)
- Git integration (bi-directional sync)
- OTA updates (instant translation pushes)
- Built-in machine translation (ChatGPT, DeepL)
- Translation memory (reuse past translations)
Developer experience:
Terminal1# Day 1: Setup 2npx @intlpullhq/cli init 3 4# Daily workflow 5npx @intlpullhq/cli upload # Upload new strings 6npx @intlpullhq/cli download # Download translations 7 8# Deploy 9npm run build # Translations bundled automatically
Translators work in the web UI. Developers never leave their terminal.
Migration Path
If you're currently using just the Intl API and JSON files, here's how to add a TMS:
Step 1: Audit Current Setup
Terminal1# Count your translation files 2find . -name "*.json" -path "*/locales/*" | wc -l 3 4# Count your translation keys 5cat locales/en.json | jq 'keys | length'
If you have <100 keys and 1-2 languages, you might be fine without a TMS.
If you have 500+ keys and 3+ languages, a TMS will save you hours weekly.
Step 2: Choose a TMS
Evaluate based on:
- Pricing (check enterprise features vs your needs)
- Git integration (do you want translations in Git?)
- OTA support (mobile app? You'll want this)
- Frameworks (React, Vue, React Native SDKs?)
- API quality (good docs, rate limits)
Step 3: Import Existing Translations
Most TMSs support JSON import:
Terminal# IntlPull example npx @intlpullhq/cli import --format json --file locales/en.json --language en npx @intlpullhq/cli import --format json --file locales/fr.json --language fr
Step 4: Set Up CI/CD
Add translation sync to your pipeline:
YAML1# GitHub Actions example 2- name: Pull latest translations 3 run: npx @intlpullhq/cli download 4 env: 5 INTLPULL_API_KEY: ${{ secrets.INTLPULL_API_KEY }} 6 7- name: Build app 8 run: npm run build
Step 5: Invite Translators
Give translators access to the TMS. They'll never touch your code again.
Real-World Examples
Company A: Stuck with Browser API Only
- SaaS app, 12 languages
- Translations in JSON files in Git
- Translators submit PRs (!)
- 40% of PRs are translation updates
- Developers spend 5 hours/week reviewing translation PRs
- Translation errors make it to production regularly
Annual cost: ~260 developer hours = $26,000 (assuming $100/hour)
Company B: Using IntlPull
- SaaS app, 15 languages
- Translations managed in IntlPull
- Translators use web UI, zero PRs
- Developers run
npx @intlpullhq/cli downloadbefore deploys - OTA updates fix errors instantly
- Developers spend 0 hours on translation management
Annual cost: $348 (IntlPull: $29/month) Savings: $25,652/year
The Bottom Line
The browser Intl API is excellent at what it does: formatting. Use it for dates, numbers, currencies, and locale-aware display logic.
But internationalization is more than formatting. It's:
- Managing translated content
- Collaborating with translators
- Automating workflows
- Deploying updates quickly
For anything beyond a simple app with 1-2 languages, you'll want a translation management system.
Start with the Intl API for formatting. Add a TMS when you have more than 2 languages or more than 100 translation keys. Your future self will thank you.
Ready to stop managing translations manually?
Try IntlPull free. CLI-first TMS with Git integration, OTA updates, and visual context for translators. No credit card required.
Or stick with DIY if you're still in the "100 keys, 2 languages" zone. We'll be here when you outgrow it.
