refactor(i18n): migrate from react-i18next to next-intl and consolidate dictionary files#16
refactor(i18n): migrate from react-i18next to next-intl and consolidate dictionary files#16fishman wants to merge 172 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedToo many files! This PR contains 163 files, which is 13 over the limit of 150. To get a review, narrow the scope: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (137)
📒 Files selected for processing (163)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request performs a major refactoring to migrate the internationalization framework from react-i18next to next-intl with localized routing, upgrades Next.js to 15.5.18, and integrates Shadcn UI components. The review feedback highlights several critical issues that must be addressed before merging: the deletion of the /api/contact route breaks form submissions on multiple pages; several links use single quotes instead of backticks, resulting in broken literal string interpolations; missing translation message providers in layout.tsx and error.tsx will cause runtime crashes; and the refactored middleware removes essential security headers. Additionally, the configuration in next.config.ts contains an invalid qualities property, and some minor cleanup is needed for duplicate paragraphs and unused components.
|
|
||
| // Send to API route using Resend | ||
|
|
||
| const response = await fetch('/api/contact', { |
There was a problem hiding this comment.
| formData.append('_template', 'box'); | ||
|
|
||
| // Send to API route using Resend | ||
| const response = await fetch('/api/contact', { |
There was a problem hiding this comment.
The /api/contact API route (src/app/api/contact/route.ts) was deleted in this pull request. However, this page (along with PricingPage.tsx and RequestDemoPage.tsx) still attempts to POST form submissions to /api/contact.
With the API route deleted, all form submissions on the website will fail with a 404 Not Found error. Please restore the /api/contact route or update the forms to use the newly added HubSpotForm component if that was the intended migration.
| export default function Error({ | ||
| params, | ||
| error, | ||
| reset, | ||
| }: { | ||
| params: Promise<{ locale: string }>; | ||
| error: Error & { digest?: string }; | ||
| reset: () => void; | ||
| }) { | ||
| useEffect(() => { | ||
| // Log the error to an error reporting service | ||
| console.error(error); | ||
| }, [error]); | ||
| const { locale } = use(params); | ||
| setRequestLocale(locale); | ||
| const t = useTranslations("error"); |
There was a problem hiding this comment.
Critical Issues with error.tsx:
paramsis undefined: Next.jserror.jscomponents do not receiveparamsas a prop. Therefore,paramswill beundefinedat runtime, and callinguse(params)on line 25 will throw a TypeError, crashing the error page itself.- Missing
NextIntlClientProvider: Since this file is located atsrc/app/error.tsx(outside the[locale]directory), it is not wrapped by theRootLayoutwhich provides theNextIntlClientProvider. As a result, callinguseTranslations("error")will throw an error because the translation context is missing.
Solution:
Move this file to src/app/[locale]/error.tsx so it is wrapped by the localized layout. Once moved inside [locale], you can remove the params prop and setRequestLocale call entirely, and use useLocale() or let next-intl handle the context automatically.
"use client";
import { ServerCrash } from "lucide-react";
import { useEffect } from "react";
import { Button } from "@/components/ui/button";
import { Link } from "@/i18n/navigation";
import { useTranslations } from "next-intl";
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error(error);
}, [error]);
const t = useTranslations("error");
return (
<div className="flex flex-col items-center justify-center min-h-[70vh] px-4 text-center">
<div className="flex items-center justify-center w-20 h-20 mb-6 rounded-full bg-red-100">
<ServerCrash className="w-10 h-10 text-red-600" />
</div>
<h1 className="mb-2 text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">
{t("title")}
</h1>
<p className="mb-8 text-lg text-gray-600">{t("sorry")}</p>
<div className="flex flex-col gap-4 sm:flex-row">
<Button onClick={() => reset()} variant="default">
{t("tryAgain")}
</Button>
<Button variant="outline" asChild>
<Link href="/">{t("returnHome")}</Link>
</Button>
</div>
</div>
);
}
| Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. | ||
| </p> | ||
| </div> |
…cales
- Replace union type `'en' | 'zh'` with `string` (`locale`/`language`) in blog server, client, and rehype plugins
- Update `formatDate` to use `locale` parameter with `toLocaleDateString`
- Change image caption language detection from `=== 'zh'` to `startsWith('zh')`
- Simplify blog post metadata lookup to use `locale` directly instead of fallback logic
Centralize canonical/alternate URL generation using locale-aware utility functions, removing duplicated inline logic from the blog post page and the generatePageMetadata function.
Previously, Chinese locale pages had incorrect canonical and alternate hreflang links (e.g., `/zh/zh`). Now uses `localizedUrl` and `localizedAlternates` utilities to generate proper URLs.
…nctions Replace manual locale-based URL generation and alternates definition across all case-study, cookies-policy, faq, privacy-policy, videos, and what-is-hami pages with the localizedUrl and localizedAlternates helpers from @/utils/seo. This centralizes URL logic and reduces duplication.
… apply in header navigation
- Add complete German translation dictionary (de.json) - Register "de" as a supported locale in routing, site config, and types - Refactor blog components to accept string locale instead of union types - Add safe locale loading with fallback to default in i18n/request.ts - Simplify FormSuccessMessage by removing per-locale fallbacks - Use routing.locales in generateStaticParams for DRYness
- Remove hardcoded locale list and labels in LanguageSwitcher
…ny before resources and community
- Move attachment page under [locale] directory for Next.js App Router i18n - Use next-intl translations for "not found" fallback and metadata - Add "attachments.notFound" translation keys for de, en, zh - Delete redundant src/app/zh/attachments/[slug]/page.tsx
…pages
- Replace `t('...', { returnObjects: true })` with `t.raw('...')` in FeatureComparisonTable, PricingPage, and SolutionsPage
- Remove unused Kantaloupe product page and Request Demo page
- Add missing `t` to useEffect dependency array in BlogPostClient
This reverts commit 0850bd0.
…oded locale logic
Replace ad-hoc locale branching and inline dictionaries (`CHROME`, `COPY`, `PRODUCT_INTRO`, `STATUS_LABEL`, `COMPATIBILITY_LABELS`, etc.) with next-intl translation keys. This eliminates props drilling of `locale`, unifies URL generation via `localizedPath()`, and moves all product metadata (`productsData`, `productIntro`) into JSON dictionaries for the three supported locales.
**Why this was needed** – The old approach had a number of problems that hurt long-term maintainability:
1. **Duplication & drift** – The same English/Chinese text was defined in multiple places (e.g., status labels appeared in `ArtifactRow`, `ProductCard`, `ProductHero`). Any update required finding and changing every copy.
2. **Non-idiomatic patterns** – Hardcoded objects like `const CHROME = { en: { … }, zh: { … } }` bypass the established `next-intl` infrastructure, making it harder to add new locales or use features like interpolation, plurals, and fallbacks.
3. **Prop drilling** – `locale` was threaded through component trees (e.g., `EnterpriseDetailClient` → `DownloadDeliveryTabs` → `ArtifactList` → `ArtifactRow`), increasing cognitive load and making components less reusable.
4. **Inconsistent handling** – Some strings used `pickI18n()` (a custom helper), others used `locale === 'zh'` ternaries directly, and some were embedded in JSX with no translation path at all.
Moving everything to `next-intl` dictionaries fixes these issues by enforcing a single source of truth, improving discoverability, and simplifying the component API.
Key changes:
- Replace `pickI18n` utility and `Locale` type with `useLocale()` and indexed `I18nText` lookups
- Remove `locale` prop from all enterprise components (`ArtifactRow`, `DownloadDeliveryTabs`, `EnterpriseDetailClient`, `ProductCard`, etc.)
- Move hardcoded copy into JSON translation dictionaries (status, compatLabels, origin, scope, productsData, productIntro, codeLabels, etc.)
- Replace conditional `locale === 'zh'` URL logic with `localizedPath()` utility
- Update `enhanceCodeBlocks` to accept a `labels` object instead of a locale string
- Switch all `@/utils/seo` imports to `@/utils/i18n`
- Unify `productsData` and `productIntro` lookups via `t.raw()` for per-locale product metadata
- Add `back` key to `installDoc` translations for all three locales
- Fix i18n interpolation syntax from `{{count}}` to `{count}`
…`[slug]` route
Replace 7 individual case study page files under `src/app/[locale]/case-studies/{slug}/` with a single dynamic route `[slug]/page.tsx`. The new page uses a `CASE_STUDIES` mapping to resolve the correct component and i18n key, removing duplicated page setups, metadata generation, and imports. This follows Next.js dynamic routing patterns, adheres to the DRY principle, and simplifies adding new case studies in the future.
…ontmatter
Replace hardcoded `'en' | 'zh'` union types with `string` in attachment and enterprise-docs modules. Remove the `frontmatter.language` field from both output types and stop deriving locale from frontmatter. Use the passed-in `locale` parameter directly for markdown conversion.
Dynamically validate product IDs against `getProductIds()` instead of a static union. Install doc default locale changed from `'zh'` to `'en'`. Locale suffix regex relaxed from `\.(en|zh)$` to `\.[a-z]{2}$`. `installDocExists` now checks via `getInstallDocSlugs()` instead of enumerating known locales.
The conditional `if (!mounted)` block returned a completely different DOM tree during SSR, causing React hydration errors. Replaced it with a synchronous logo placeholder (`/dynamia-logo.svg`) for SSR and deferred locale/theme-aware logo selection to after mount. Also switched the logo source from hardcoded SVG paths to translation keys (`t('navigation.logo')` / `t('navigation.logoDark')`), enabling locale-aware branding. This ensures the component tree is identical between server and client, preventing `useId()` mismatches.
…lated link Replace inline consent label markup with a shared `ConsentLabel` component that uses `t.rich` for i18n-safe link rendering.
src/i18n/locales/to top-leveldictionary/directoryreact-i18nextwithnext-intlacross all components and pageszh/route pages in favor of locale-based routing via next-intl middlewarenext.config.mjs→next.config.tsand switch to next-intl plugini18next/react-i18nextfornext-intl, upgrade Next.js to 15.5, add shadcn dependencies