Skip to content

feat: add multi-language support (Vietnamese & English)#1

Open
justccuong wants to merge 3 commits intoLiusDev:mainfrom
justccuong:feat/add-multilanguage-support
Open

feat: add multi-language support (Vietnamese & English)#1
justccuong wants to merge 3 commits intoLiusDev:mainfrom
justccuong:feat/add-multilanguage-support

Conversation

@justccuong
Copy link
Copy Markdown

@justccuong justccuong commented Apr 3, 2026

Thêm nút chuyển ngôn ngữ trên Header
URL có prefix locale: /vi/... hoặc /en/...
Dịch Sidebar, Table, Pagination, các nút chung
Mặc định: Tiếng Việt

Summary by CodeRabbit

  • New Features

    • English and Vietnamese localizations added across branding, navigation, auth, chat, and entity UIs
    • Language toggle in the header lets users switch languages without leaving the current page
  • Routing & Behavior

    • App routing and middleware now honor locale-aware paths; root now redirects to the Vietnamese LLMs page by default
    • Navigation labels and sidebar now display localized text
  • Chores

    • Added a stable dev script and integrated internationalization support

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23f31dfb-2005-4355-8297-34f16a45f287

📥 Commits

Reviewing files that changed from the base of the PR and between 5ea0f33 and 4254749.

📒 Files selected for processing (1)
  • src/middleware.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/middleware.ts

📝 Walkthrough

Walkthrough

Adds full i18n: English and Vietnamese message files, next-intl integration (plugin, middleware, routing, request loader), locale-aware RootLayout, language toggle UI, localized components, updated route matchers/CORS for API, package/script updates, and Cloudflare D1/KV id updates.

Changes

Cohort / File(s) Summary
Localization Files
messages/en.json, messages/vi.json
Add English and Vietnamese translation JSONs with keys under Common, Navigation, Auth, Chat, and Entity.
Next.js Config & Scripts
next.config.ts, package.json
Initialize next-intl plugin in Next config; wrap export with plugin; change root redirect to /vi/llms; add next-intl dependency and dev:stable script.
Locale-aware Layout
src/app/layout.tsx, src/app/[locale]/layout.tsx
Remove top-level src/app/layout.tsx; add src/app/[locale]/layout.tsx RootLayout that validates locale, loads messages, sets html lang, and wraps children with NextIntlClientProvider and app providers (QueryProvider, NuqsAdapter, ThemeProvider, Toaster).
i18n Core
src/i18n/routing.ts, src/i18n/request.ts
Add routing config (vi,en, default vi, localePrefix: 'always'), navigation helpers (Link, useRouter, usePathname, etc.), and request config that resolves locale and dynamically imports messages.
Middleware & Routing Matcher
src/middleware.ts
Integrate next-intl middleware; split middleware to handle CORS for /api and delegate non-API requests to i18n middleware; expand config.matcher to include locale and non-asset routes.
UI Components (i18n)
src/components/language-toggle.tsx, src/components/app-header.tsx, src/components/app-sidebar.tsx, src/components/entity-components.tsx
Add LanguageToggle component and render it in AppHeader; replace next/link/navigation with i18n routing bindings; convert hardcoded labels to useTranslations() calls and adjust default label behaviors for entity components.
Cloudflare Config
wrangler.jsonc
Update D1 database database_id and KV namespace id values.

Sequence Diagram

sequenceDiagram
    participant User
    participant LT as LanguageToggle
    participant Router as Router
    participant Middleware as i18nMiddleware
    participant Routing as RoutingConfig
    participant Loader as MessageLoader
    participant Layout as [locale] Layout
    participant UI as UI Components

    User->>LT: select language
    LT->>Router: replace(pathname, { locale: newLocale })
    Router->>Middleware: incoming request with locale
    Middleware->>Routing: validate locale & routing rules
    Routing-->>Middleware: locale resolved
    Middleware->>Loader: load messages for locale
    Loader-->>Layout: return messages
    Layout->>UI: NextIntlClientProvider supplies messages
    UI-->>User: render localized UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I hop from vi to en with a joyful twitch,
Messages fetched, locales stitched without a hitch,
Middleware watches paths, router keeps the pace,
Layout shares the words, UI greets each face,
Two tongues now chatter in our cozy little space.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding multi-language support for Vietnamese and English locales throughout the application.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
next.config.ts (1)

16-16: Avoid hardcoding locale in root redirect to preserve middleware locale detection.

Line 16 hardcodes /vi/llms, which bypasses the i18n middleware's locale detection mechanism (Accept-Language header, stored preferences, etc.). Even though defaultLocale is set to 'vi', this hardcoded redirect forces Vietnamese regardless of user preference. Redirect to /llms instead and let the middleware handle locale prefix selection:

Suggested change
-				destination: "/vi/llms",
+				destination: "/llms",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@next.config.ts` at line 16, The redirect currently hardcodes a Vietnamese
locale by setting destination: "/vi/llms", which bypasses Next.js i18n
middleware; update the redirect configuration (the object with destination:
"/vi/llms" in next.config.ts, typically returned from the redirects() export) to
use destination: "/llms" so the middleware and i18n settings can apply locale
prefixes dynamically instead of forcing "vi".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`[locale]/layout.tsx:
- Around line 25-28: metadata currently contains a hard-coded Vietnamese
description; change it to be generated per-locale by replacing the static export
const metadata with a generateMetadata function (or add export async function
generateMetadata) that reads the resolved locale (e.g., params.locale or the
same locale resolution used in this layout) and returns a metadata object with
localized title/description strings so /en/* pages get English SEO/share text;
reference the metadata symbol and implement generateMetadata in layout.tsx to
produce the localized metadata.

In `@src/components/entity-components.tsx`:
- Around line 126-131: EntityHeader is using next/link directly for internal
routes (the block with newButtonHref && !onNew rendering Button -> Link ->
PlusIcon/label), which skips locale prefixes when routing; replace the imported
Link from 'next/link' with the app's locale-aware Link wrapper (the custom Link
component used elsewhere) so internal links are locale-prefixed; update the
import where Link is brought into src/components/entity-components.tsx and
ensure the JSX uses that wrapper (no other JSX changes required for the
newButtonHref/onNew branch or the other call site referenced).

In `@src/components/language-toggle.tsx`:
- Line 28: Replace the hardcoded screen-reader text in the LanguageToggle
component by using the i18n/messages lookup instead of the literal "Toggle
language": locate the span with className "sr-only" in
src/components/language-toggle.tsx and render the localized string
(Common.toggleLanguage) from your translation/messages provider (e.g., useIntl,
t, or the project's i18n helper used elsewhere in the component). Then add the
key "Common.toggleLanguage" with appropriate values to both messages/en.json and
messages/vi.json so the translated label is available for English and
Vietnamese.

In `@src/middleware.ts`:
- Around line 57-70: The matcher in export const config currently excludes API
routes so the CORS/preflight branch never runs; update the config.matcher array
in middleware.ts (the export const config and its matcher) to include the
'/api/:path*' pattern (in addition to existing entries like '/' and
'/(vi|en)/:path*' and the general redirect pattern) so that API requests hit the
middleware and the Access-Control-* and preflight handling code executes.

In `@wrangler.jsonc`:
- Line 46: The wrangler.jsonc is using global binding IDs ("database_id":
"72839a05-137f-4f25-baf8-71f8c127b3c1" and KV "id":
"0c06c7bedbfe492daf7acda7dcf3e7ac") which makes all environments share
production resources; update the config to scope these bindings per environment
by moving the D1 and KV binding entries from the root into environment-specific
blocks (e.g., [env.staging], [env.production], or separate
wrangler.staging.jsonc/wrangler.production.jsonc) and set environment-specific
values for the database_id and id for each environment so dev/staging do not
point at the production IDs.

---

Nitpick comments:
In `@next.config.ts`:
- Line 16: The redirect currently hardcodes a Vietnamese locale by setting
destination: "/vi/llms", which bypasses Next.js i18n middleware; update the
redirect configuration (the object with destination: "/vi/llms" in
next.config.ts, typically returned from the redirects() export) to use
destination: "/llms" so the middleware and i18n settings can apply locale
prefixes dynamically instead of forcing "vi".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d0925b43-e701-43b1-b598-23739e4ba748

📥 Commits

Reviewing files that changed from the base of the PR and between f65fd10 and a298ccb.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (25)
  • messages/en.json
  • messages/vi.json
  • next.config.ts
  • package.json
  • src/app/[locale]/(auth)/layout.tsx
  • src/app/[locale]/(auth)/login/page.tsx
  • src/app/[locale]/(auth)/signup/page.tsx
  • src/app/[locale]/(dashboard)/(admin)/agents/page.tsx
  • src/app/[locale]/(dashboard)/(admin)/datasources/[id]/page.tsx
  • src/app/[locale]/(dashboard)/(admin)/datasources/page.tsx
  • src/app/[locale]/(dashboard)/(admin)/layout.tsx
  • src/app/[locale]/(dashboard)/(admin)/llms/page.tsx
  • src/app/[locale]/(dashboard)/layout.tsx
  • src/app/[locale]/(dashboard)/playground/page.tsx
  • src/app/[locale]/globals.css
  • src/app/[locale]/layout.tsx
  • src/app/layout.tsx
  • src/components/app-header.tsx
  • src/components/app-sidebar.tsx
  • src/components/entity-components.tsx
  • src/components/language-toggle.tsx
  • src/i18n/request.ts
  • src/i18n/routing.ts
  • src/middleware.ts
  • wrangler.jsonc
💤 Files with no reviewable changes (1)
  • src/app/layout.tsx

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/components/entity-components.tsx (2)

103-105: Consider using a sentinel pattern for consistency.

The string comparison newButtonLabel === "New" is fragile—if the default ever changes, this comparison silently breaks. Other components in this file use the cleaner prop || t(key) pattern. Consider aligning for consistency:

♻️ Alternative using undefined sentinel
 type EntityHeaderProps = {
     title: string
     description?: string
-    newButtonLabel?: string
+    newButtonLabel?: string  // undefined = use translated default
     disabled?: boolean
     isCreating?: boolean
 } & (...)

 export const EntityHeader = ({
     ...
-    newButtonLabel = "New",
+    newButtonLabel,
     ...
 }: EntityHeaderProps) => {
     const t = useTranslations("Entity")
-    const label = newButtonLabel === "New" ? t("new") : newButtonLabel
+    const label = newButtonLabel ?? t("new")

Note: This would be a minor breaking change if any caller explicitly passes undefined expecting "New".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/entity-components.tsx` around lines 103 - 105, Replace the
fragile string comparison for label with the sentinel pattern used elsewhere:
instead of checking newButtonLabel === "New", use the prop-or-default pattern
with the translation (e.g., use newButtonLabel ?? t("new") or newButtonLabel ||
t("new") depending on whether you want to treat empty string as a value) so the
default translation from useTranslations("Entity") is used when newButtonLabel
is undefined; update the assignment to label to follow this pattern (refer to
newButtonLabel, t, and label).

566-566: Same fragile string comparison pattern.

This mirrors the pattern in EntityHeader. Consider using emptyMessage ?? t("noResults") with no default param for consistency with the rest of the file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/entity-components.tsx` at line 566, Replace the fragile string
comparison in the JSX expression that currently checks emptyMessage === "No
results." with a nullish coalescing pattern; update the expression around
emptyMessage and t("noResults") (same area shown in EntityHeader) to use
emptyMessage ?? t("noResults") and remove any hard-coded default param usage so
it follows the file's existing convention of relying on t("noResults") only when
emptyMessage is null/undefined.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/middleware.ts`:
- Around line 33-48: When Access-Control-Allow-Origin is set dynamically
(originAllowed), add a Vary: Origin header so caches don't serve the wrong CORS
headers: in the OPTIONS branch include 'Vary': 'Origin' in the preflightHeaders
object, and after you call response.headers.set('Access-Control-Allow-Origin',
origin) ensure you also add or append 'Vary' => 'Origin' on the response.headers
(preserving any existing Vary values rather than overwriting) using the same
headers API used elsewhere in middleware.ts.

---

Nitpick comments:
In `@src/components/entity-components.tsx`:
- Around line 103-105: Replace the fragile string comparison for label with the
sentinel pattern used elsewhere: instead of checking newButtonLabel === "New",
use the prop-or-default pattern with the translation (e.g., use newButtonLabel
?? t("new") or newButtonLabel || t("new") depending on whether you want to treat
empty string as a value) so the default translation from
useTranslations("Entity") is used when newButtonLabel is undefined; update the
assignment to label to follow this pattern (refer to newButtonLabel, t, and
label).
- Line 566: Replace the fragile string comparison in the JSX expression that
currently checks emptyMessage === "No results." with a nullish coalescing
pattern; update the expression around emptyMessage and t("noResults") (same area
shown in EntityHeader) to use emptyMessage ?? t("noResults") and remove any
hard-coded default param usage so it follows the file's existing convention of
relying on t("noResults") only when emptyMessage is null/undefined.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b8eac385-93b5-4c84-8cee-23d3a8ed5294

📥 Commits

Reviewing files that changed from the base of the PR and between a298ccb and 5ea0f33.

📒 Files selected for processing (5)
  • messages/en.json
  • messages/vi.json
  • src/components/entity-components.tsx
  • src/components/language-toggle.tsx
  • src/middleware.ts
✅ Files skipped from review due to trivial changes (2)
  • messages/vi.json
  • messages/en.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/language-toggle.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant