Multi-Language Support – Implementation Plan
Status: Planning
Target Language: French (fr-CA / fr-FR)
Audience: Engineering team
This document outlines the plan for adding multi-language support to QuoteStore, starting with French translation.
1. Goals
- Language Dropdown – Add a visible language switcher in the storefront header that lets buyers toggle between English and French.
- Automatic Translation – Translate all static UI strings (buttons, labels, navigation, messages) into French.
- SEO-Friendly – Ensure translated pages are indexable and have proper
hreflangtags. - Minimal Friction – Buyers should be able to switch languages instantly without page reload.
- Future-Proof – Architecture should support adding more languages later (Spanish, etc.).
2. Scope
In Scope (Phase 1)
| Area | What gets translated |
|---|---|
| Storefront UI | Navigation, buttons, form labels, validation messages, empty states, tooltips |
| Checkout flow | Cart, shipping, payment, order confirmation |
| Account pages | Profile, addresses, team management |
| Emails (stretch) | Order confirmations, status updates |
Out of Scope (Phase 1)
| Area | Reason |
|---|---|
| Product names & descriptions | Dynamic content from AutoQuotes; would require dealer-side translation workflow |
| Dealer portal | Lower priority; dealers are primarily English-speaking |
| Admin portal | Internal use only |
| User-generated content | Comments, notes, etc. |
3. Solution Options
Option A: Weglot (SaaS)
How it works: Third-party service that intercepts and translates content on-the-fly.
| Pros | Cons |
|---|---|
| Zero code changes to existing components | Monthly cost (~$15–$200/mo depending on word count) |
| Visual editor for translation refinements | External dependency |
| SEO-friendly URLs out of the box | Privacy/data considerations |
| Works immediately | Less control over translation quality |
Integration:
// layout.tsx or providers.tsx
import { WeglotProvider } from '@weglot/react';
export default function RootLayout({ children }) {
return (
<WeglotProvider apiKey={process.env.NEXT_PUBLIC_WEGLOT_API_KEY}>
{children}
</WeglotProvider>
);
}Estimated effort: 1–2 hours setup + ongoing subscription cost.
Option B: next-intl (Recommended)
How it works: First-party i18n library for Next.js App Router. We maintain JSON translation files.
| Pros | Cons |
|---|---|
| Free & open source | Requires creating translation files |
| Full control over translations | More upfront work |
| Type-safe with TypeScript | Need to wrap text in t() calls |
| Excellent App Router / RSC support | — |
| SEO-friendly with proper routing | — |
File structure:
apps/storefront/
├── messages/
│ ├── en.json
│ └── fr.json
├── i18n/
│ ├── request.ts
│ └── routing.ts
└── middleware.ts (locale detection)Example usage:
// messages/en.json
{
"nav": {
"catalog": "Browse Catalog",
"cart": "Cart",
"orders": "Orders",
"account": "Account"
},
"cart": {
"empty": "Your cart is empty",
"checkout": "Proceed to Checkout",
"total": "Total"
}
}
// messages/fr.json
{
"nav": {
"catalog": "Parcourir le catalogue",
"cart": "Panier",
"orders": "Commandes",
"account": "Compte"
},
"cart": {
"empty": "Votre panier est vide",
"checkout": "Passer à la caisse",
"total": "Total"
}
}// Component usage
import { useTranslations } from 'next-intl';
export function CartPage() {
const t = useTranslations('cart');
return (
<div>
<h1>{t('checkout')}</h1>
<p>{t('empty')}</p>
</div>
);
}Estimated effort: 2–3 days for full storefront coverage.
Option C: Google Translate Widget (Quick & Free)
How it works: Embed Google’s translate widget; client-side machine translation.
| Pros | Cons |
|---|---|
| Free | Unprofessional appearance |
| 2-minute setup | Not SEO-friendly (no indexable translated pages) |
| Supports 100+ languages | Translation quality varies |
| — | Google may deprecate at any time |
| — | Flash of untranslated content |
Not recommended for a B2B platform where professionalism matters.
4. Recommendation
Use next-intl (Option B) for the following reasons:
- Cost: Free, no ongoing subscription.
- Quality: We control translations; can have a native French speaker review.
- Performance: No external API calls; translations are bundled.
- SEO: Proper
/en/...and/fr/...URL structure withhreflangtags. - Future-proof: Easy to add Spanish, etc. later.
- Type-safety: Catches missing translation keys at build time.
For the initial French translations, we can:
- Use ChatGPT/Claude to generate the
fr.jsonfile fromen.json - Have a native speaker review and refine
- Iterate based on buyer feedback
5. Implementation Plan
Phase 1: Foundation (Day 1)
- Install
next-intlpackage - Create
messages/en.jsonwith all static strings - Configure i18n routing in
middleware.ts - Add locale prefix to App Router (
/[locale]/...) - Create
LocaleSwitchercomponent for header
Phase 2: String Extraction (Day 1–2)
- Audit all storefront pages for hardcoded strings
- Replace hardcoded strings with
t()calls - Group translations logically:
nav,cart,checkout,orders,account,common
Phase 3: French Translations (Day 2)
- Generate
fr.jsonusing AI translation - Review with native French speaker (if available)
- Test all pages in French locale
Phase 4: Polish (Day 3)
- Add language switcher dropdown to header (flag + language name)
- Persist language preference in cookie/localStorage
- Add
hreflangmeta tags for SEO - Test locale detection from browser
Accept-Language - Update email templates (if in scope)
Phase 5: QA & Launch
- Full storefront walkthrough in both languages
- Test checkout flow end-to-end in French
- Verify no missing translation keys (TypeScript will catch most)
- Deploy to staging → production
6. UI/UX Design
Language Switcher Location
Place in the header, right side, near the account/cart icons:
┌─────────────────────────────────────────────────────────────┐
│ 🏠 QuoteStore Catalog Orders Reorder Lists │ 🌐 EN ▾ │ 👤 │ 🛒 │
└─────────────────────────────────────────────────────────────┘
├── English
└── FrançaisDropdown Design
<DropdownMenu>
<DropdownMenuTrigger>
<Globe className="h-4 w-4" />
<span>{currentLocale.toUpperCase()}</span>
<ChevronDown className="h-3 w-3" />
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>🇺🇸 English</DropdownMenuItem>
<DropdownMenuItem>🇫🇷 Français</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>7. Technical Details
URL Structure
| English | French |
|---|---|
/catalog | /fr/catalog |
/cart | /fr/cart |
/orders/123 | /fr/orders/123 |
Default locale (English) can optionally omit the prefix for cleaner URLs.
Middleware
// middleware.ts
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
export default createMiddleware(routing);
export const config = {
matcher: ['/', '/(fr|en)/:path*']
};Server Components
next-intl supports React Server Components:
// Server Component
import { getTranslations } from 'next-intl/server';
export default async function CatalogPage() {
const t = await getTranslations('catalog');
return <h1>{t('title')}</h1>;
}Client Components
'use client';
import { useTranslations } from 'next-intl';
export function AddToCartButton() {
const t = useTranslations('cart');
return <Button>{t('addToCart')}</Button>;
}8. Translation File Structure
// messages/en.json
{
"common": {
"loading": "Loading...",
"error": "Something went wrong",
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"edit": "Edit",
"search": "Search",
"noResults": "No results found"
},
"nav": {
"catalog": "Catalog",
"cart": "Cart",
"orders": "Orders",
"reorderLists": "Reorder Lists",
"account": "Account",
"signOut": "Sign Out"
},
"catalog": {
"title": "Browse Products",
"filters": "Filters",
"sortBy": "Sort by",
"addToCart": "Add to Cart",
"viewDetails": "View Details"
},
"cart": {
"title": "Shopping Cart",
"empty": "Your cart is empty",
"subtotal": "Subtotal",
"checkout": "Proceed to Checkout",
"continueShopping": "Continue Shopping",
"remove": "Remove",
"quantity": "Quantity"
},
"checkout": {
"title": "Checkout",
"shipping": "Shipping Address",
"review": "Review Order",
"placeOrder": "Place Order",
"orderPlaced": "Order Placed Successfully!"
},
"orders": {
"title": "Your Orders",
"orderNumber": "Order #",
"status": "Status",
"total": "Total",
"viewDetails": "View Details",
"noOrders": "You haven't placed any orders yet"
},
"account": {
"title": "Account Settings",
"profile": "Profile",
"addresses": "Addresses",
"team": "Team Members"
}
}9. Risks & Mitigations
| Risk | Mitigation |
|---|---|
| Translation quality issues | Have native speaker review; iterate based on feedback |
| Missing translations in edge cases | TypeScript strict mode catches at build time |
| Performance impact | Translations are static; no runtime overhead |
| Dynamic content (product names) | Out of scope for Phase 1; document as future work |
| Pluralization edge cases | next-intl has built-in ICU message format support |
10. Future Considerations
- More languages: Spanish (es), German (de) based on customer demand
- Product translation: Workflow for dealers to provide translated product names/descriptions
- RTL support: Arabic, Hebrew if needed (requires additional CSS work)
- Translation management UI: Admin interface to edit translations without code deploy
- AI-assisted translation: Integration with DeepL API for automatic draft translations