-
-
Notifications
You must be signed in to change notification settings - Fork 0
✨ add i18n #365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
✨ add i18n #365
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughThis PR introduces Changes
Sequence Diagram(s)sequenceDiagram
participant App as React App
participant Provider as TranslationProvider
participant Context as TranslationContext
participant Hook as useTranslation Hook
participant Loader as TranslationLoader
participant Dict as Translation Dictionary
participant Component as Page Component
App->>Provider: Mount with locales, defaultLocale, load fn
Provider->>Provider: Validate locales, set default, init locale state
Provider->>Context: Provide TranslationContextValue (locale, load, setLocale)
Component->>Hook: Call useTranslation("common")
Hook->>Hook: useTranslationLoader("common")
Hook->>Context: useTranslationContext()
Context-->>Hook: Return { locale, defaultLocale, load, keySplit, setLocale }
Hook->>Loader: Call load(locale, "common")
Loader->>Dict: Load JSON for locale
Dict-->>Loader: Return TranslationDictionary
Hook->>Hook: createTranslate(dictionary, keySplit)
Hook-->>Component: Return { t, locale }
Component->>Component: t("greeting", { name: "Alice" })
Component->>Component: Resolve dot-notation key, substitute variables
Component-->>Component: "Hello Alice!"
Component->>Component: setLocale("fr")
setLocale->>Provider: Update locale state
Provider->>Context: Emit new context value
Context-->>Hook: Trigger re-render with new locale
Hook->>Loader: Call load("fr", "common")
Hook-->>Component: Re-render with French translations
rect rgba(100, 150, 255, 0.1)
note over Component: User sees translated content updated
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (53)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds a comprehensive internationalization (i18n) library @onruntime/translations with support for React, Next.js (App Router and Pages Router), and Vite applications.
Key changes:
- New
@onruntime/translationspackage with modular exports for React, Next.js, and Vite - Three example applications demonstrating different use cases
- Dependency updates (
@vercel/og,lucide-react, GitHub Actions checkout)
Reviewed changes
Copilot reviewed 53 out of 54 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
packages/translations/* |
Core i18n library with TypeScript, React hooks, Next.js providers, and Vite loader |
examples/translations/react/* |
React + Vite example with locale switching |
examples/translations/next-app/* |
Next.js App Router example with middleware-based routing |
examples/translations/next-pages/* |
Next.js Pages Router example with built-in i18n |
pnpm-workspace.yaml |
Added examples directory to workspace |
pnpm-lock.yaml |
Updated dependencies and added new packages |
apps/web/package.json |
Updated @vercel/og and lucide-react versions |
.github/workflows/checks.yml |
Updated checkout action version |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| load, | ||
| keySplit = true, | ||
| }: TranslationProviderProps) => { | ||
| const resolvedDefaultLocale = defaultLocale ?? locales[0]; |
Copilot
AI
Dec 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The locales array should be validated to ensure it's not empty before using locales[0] as the default locale. If an empty array is passed, this will result in undefined being used as the default locale, which could cause runtime errors throughout the application.
| return ( | ||
| <div style={{ fontFamily: "system-ui", padding: "2rem" }}> | ||
| <h1>{t("greeting", { name: "John" })}</h1> | ||
| <p>{t(count === 1 ? "messages.one" : "messages.other", { count })}</p> |
Copilot
AI
Dec 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition 'count === 1' is always false.
| <p>{t(count === 1 ? "messages.one" : "messages.other", { count })}</p> | |
| <p>{t("messages.other", { count })}</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (6)
examples/translations/react/src/components/demo.tsx (1)
10-10: Condition is always false.Since
countis hardcoded to5on line 5, the conditioncount === 1will always evaluate tofalse, making the ternary expression always select"messages.other". Consider either:
- Simplifying to directly use
"messages.other"if demonstrating plural form- Making
countdynamic to demonstrate both singular and plural casesexamples/translations/react/src/components/locale-switcher.tsx (1)
1-1: Import path is correct.The import path
"@onruntime/translations/react"is already using the correct subpath export as mentioned in the previous review comment.examples/translations/next-pages/pages/about.tsx (2)
2-2: Remove unused import.The
useRouterimport is not used in this component.🔎 Proposed fix
import Link from "next/link"; -import { useRouter } from "next/router";
4-4: Verify the correct import path for useTranslation.According to the package exports configuration, the
useTranslationhook should be imported from"@onruntime/translations/react"instead of"@onruntime/translations".Run the following script to verify the package exports:
#!/bin/bash # Description: Check the package.json exports for translations package cat packages/translations/package.json | jq '.exports'examples/translations/next-pages/pages/index.tsx (1)
6-34: LGTM with one note.The component correctly demonstrates the i18n patterns including translation lookup, locale switching via Next.js Link, and pluralization. The
count === 1condition on line 21 is always false sincecountis hardcoded to 5, but this appears intentional for demonstrating the pluralization syntax.packages/translations/src/react/contexts/translation-context.tsx (1)
35-36: Add validation for emptylocalesarray.If
localesis an empty array anddefaultLocaleis not provided,locales[0]will beundefined, causing the locale state to initialize asundefined. This could propagate runtime errors throughout the translation system.🔎 Proposed fix
export const TranslationProvider = ({ children, locales, defaultLocale, load, keySplit = true, }: TranslationProviderProps) => { + if (locales.length === 0) { + throw new Error("TranslationProvider: locales array must not be empty"); + } const resolvedDefaultLocale = defaultLocale ?? locales[0]; const [locale, setLocale] = useState(resolvedDefaultLocale);
🧹 Nitpick comments (8)
packages/translations/src/next/link.tsx (1)
20-24: Consider handling additional URL protocols.The external link detection only checks for
httpand//prefixes. Other protocols likemailto:,tel:,ftp:, etc., should also pass through unchanged without locale prefixing.🔎 Suggested enhancement for protocol detection
- if (href.startsWith("http") || href.startsWith("//")) { + if (href.startsWith("http") || href.startsWith("//") || href.includes(":")) { return <NextLink href={href} {...props} />; }Note: The
includes(":")check will catch most protocols (mailto:, tel:, ftp:, etc.) while still allowing relative paths without protocols.examples/translations/react/src/main.tsx (1)
6-10: Consider adding defensive check for root element.The non-null assertion operator (
!) assumes the element with id "root" exists. While this is standard in examples and the correspondingindex.htmldoes include the element, adding a defensive check would make the code more robust.🔎 Defensive alternative
-createRoot(document.getElementById("root")!).render( +const rootElement = document.getElementById("root"); +if (!rootElement) throw new Error("Root element not found"); + +createRoot(rootElement).render( <StrictMode> <App /> </StrictMode>, );packages/translations/src/next/provider.tsx (1)
29-41: Consider stability ofrouterdependency in useMemo.Including the
routerobject in theuseMemodependency array may cause the context value to be recreated on every navigation, even when locale-related values haven't changed. Since Next.js router is generally stable but its properties change on navigation, this could lead to unnecessary re-renders of consumers.Consider extracting only the needed stable values or using a ref to stabilize the setLocale callback.
🔎 Alternative approach using useCallback
+import { type ReactNode, useMemo, useCallback } from "react"; -import { type ReactNode, useMemo } from "react"; ... + const setLocale = useCallback((newLocale: string) => { + router.push(router.pathname, router.asPath, { locale: newLocale }); + }, [router]); + const value = useMemo( () => ({ locale, locales, defaultLocale, - setLocale: (newLocale: string) => { - router.push(router.pathname, router.asPath, { locale: newLocale }); - }, + setLocale, load, keySplit, }), - [locale, locales, defaultLocale, router, load, keySplit], + [locale, locales, defaultLocale, setLocale, load, keySplit], );examples/translations/next-app/app/[lang]/layout.tsx (1)
11-27: Consider adding essential metadata for production use.The layout correctly handles the async
paramspattern for Next.js 15 and properly sets thelangattribute. For a production-ready example, consider adding viewport and charset metadata.🔎 Suggested enhancement
+import type { Metadata, Viewport } from "next"; import type { ReactNode } from "react"; import { locales } from "@/lib/translations"; import { Providers } from "./providers"; +export const metadata: Metadata = { + title: "Next.js i18n Example", +}; + +export const viewport: Viewport = { + width: "device-width", + initialScale: 1, +}; + export async function generateStaticParams() {examples/translations/next-app/middleware.ts (2)
14-18: Consider using a more precise replacement to avoid edge cases.The current
replacecould behave unexpectedly in edge cases. Using a regex with start anchor is safer.🔎 Suggested fix
// If it's the default locale with prefix, redirect to remove it if (pathnameLocale === defaultLocale) { - const newPathname = pathname.replace(`/${defaultLocale}`, "") || "/"; + const newPathname = pathname.replace(new RegExp(`^/${defaultLocale}`), "") || "/"; return NextResponse.redirect(new URL(newPathname, request.url)); }
28-30: Consider expanding the matcher to exclude additional static assets.The current matcher excludes
_nextandfavicon.ico. For production use, you may want to exclude other static assets to reduce unnecessary middleware invocations.🔎 Suggested enhancement
export const config = { - matcher: ["/((?!_next|favicon.ico).*)"], + matcher: ["/((?!_next|favicon.ico|.*\\..*).*)"], };This additionally excludes paths containing a dot (like
.png,.css,.js), which typically represent static files.examples/translations/next-app/lib/translations.ts (1)
9-15: Consider adding locale validation for defense-in-depth.While the current usage is safe (locales come from static arrays), adding a validation check would provide defense-in-depth against potential misuse.
🔎 Suggested enhancement
export const load: TranslationLoader = (locale, namespace) => { + if (!locales.includes(locale)) { + return undefined; + } try { return require(`../locales/${locale}/${namespace}.json`); } catch { return undefined; } };packages/translations/src/core/types.ts (1)
12-15: Consider async loader support for dynamic imports.The synchronous
TranslationLoadertype works well withrequire()but limits usage with dynamicimport()for code-splitting scenarios. This is fine for the initial implementation, but you may want to consider supporting async loaders in the future.🔎 Optional: Add async loader type for future flexibility
// Synchronous loader (current) export type TranslationLoader = ( locale: string, namespace: string, ) => TranslationDictionary | undefined; // Async loader (future consideration) export type AsyncTranslationLoader = ( locale: string, namespace: string, ) => Promise<TranslationDictionary | undefined>;
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (53)
.github/workflows/checks.ymlapps/web/package.jsonexamples/translations/next-app/app/[lang]/about/page.tsxexamples/translations/next-app/app/[lang]/layout.tsxexamples/translations/next-app/app/[lang]/page.tsxexamples/translations/next-app/app/[lang]/providers.tsxexamples/translations/next-app/lib/translations.tsexamples/translations/next-app/locales/en/common.jsonexamples/translations/next-app/locales/fr/common.jsonexamples/translations/next-app/middleware.tsexamples/translations/next-app/next.config.tsexamples/translations/next-app/package.jsonexamples/translations/next-app/tsconfig.jsonexamples/translations/next-pages/locales/en/common.jsonexamples/translations/next-pages/locales/fr/common.jsonexamples/translations/next-pages/next.config.jsexamples/translations/next-pages/package.jsonexamples/translations/next-pages/pages/_app.tsxexamples/translations/next-pages/pages/about.tsxexamples/translations/next-pages/pages/index.tsxexamples/translations/next-pages/tsconfig.jsonexamples/translations/react/index.htmlexamples/translations/react/package.jsonexamples/translations/react/src/app.tsxexamples/translations/react/src/components/demo.tsxexamples/translations/react/src/components/locale-switcher.tsxexamples/translations/react/src/locales/en/common.jsonexamples/translations/react/src/locales/fr/common.jsonexamples/translations/react/src/main.tsxexamples/translations/react/tsconfig.jsonexamples/translations/react/vite.config.tspackages/translations/README.mdpackages/translations/package.jsonpackages/translations/src/core/index.tspackages/translations/src/core/loader.tspackages/translations/src/core/translate.tspackages/translations/src/core/types.tspackages/translations/src/index.tspackages/translations/src/next/app-provider.tsxpackages/translations/src/next/index.tspackages/translations/src/next/link.tsxpackages/translations/src/next/provider.tsxpackages/translations/src/react/contexts/translation-context.tsxpackages/translations/src/react/hooks/use-locale.tspackages/translations/src/react/hooks/use-translation-context.tspackages/translations/src/react/hooks/use-translation-loader.tspackages/translations/src/react/hooks/use-translation.tspackages/translations/src/react/index.tspackages/translations/src/vite/index.tspackages/translations/src/vite/loader.tspackages/translations/tsconfig.jsonpackages/translations/tsup.config.tspnpm-workspace.yaml
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-12-28T00:50:00.153Z
Learnt from: CR
Repo: onRuntime/onruntime PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T00:50:00.153Z
Learning: Use Turborepo with pnpm workspaces for monorepo management with turbo.json and pnpm-workspace.yaml configuration
Applied to files:
pnpm-workspace.yaml
📚 Learning: 2025-12-28T00:50:00.153Z
Learnt from: CR
Repo: onRuntime/onruntime PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T00:50:00.153Z
Learning: Applies to apps/web/env.ts : Manage environment variables via t3-oss/env-nextjs in env.ts with validation skipped in CI (skipValidation: !!process.env.CI)
Applied to files:
examples/translations/next-app/tsconfig.jsonexamples/translations/next-pages/tsconfig.json
📚 Learning: 2025-12-28T00:50:00.153Z
Learnt from: CR
Repo: onRuntime/onruntime PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T00:50:00.153Z
Learning: Applies to apps/web/src/services/**/*.{ts,tsx} : Use lazy initialization for external API clients to avoid build failures when environment variables are missing in CI, via Proxy pattern or client object pattern
Applied to files:
packages/translations/tsup.config.ts
📚 Learning: 2025-12-28T00:50:00.153Z
Learnt from: CR
Repo: onRuntime/onruntime PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T00:50:00.153Z
Learning: Applies to apps/web/src/app/**/route.{ts,tsx} : Use Next.js unstable_cache for caching external API responses in API routes
Applied to files:
examples/translations/next-app/middleware.ts
🧬 Code graph analysis (19)
examples/translations/next-app/app/[lang]/about/page.tsx (4)
examples/translations/next-pages/pages/about.tsx (1)
About(6-30)packages/translations/src/react/hooks/use-translation.ts (1)
useTranslation(11-14)packages/translations/src/react/hooks/use-locale.ts (1)
useLocale(5-8)packages/translations/src/next/link.tsx (1)
Link(14-57)
packages/translations/src/react/hooks/use-translation-context.ts (2)
packages/translations/src/react/index.ts (3)
useTranslationContext(10-10)TranslationContextValue(5-5)TranslationContext(3-3)packages/translations/src/react/contexts/translation-context.tsx (2)
TranslationContextValue(7-14)TranslationContext(16-18)
packages/translations/src/core/loader.ts (3)
examples/translations/next-app/lib/translations.ts (1)
load(9-15)packages/translations/src/core/types.ts (2)
TranslationLoader(12-15)TranslateFunction(7-10)packages/translations/src/core/translate.ts (1)
createTranslate(10-35)
examples/translations/react/src/components/demo.tsx (2)
packages/translations/src/react/hooks/use-translation.ts (1)
useTranslation(11-14)packages/translations/src/react/index.ts (1)
useTranslation(14-14)
packages/translations/src/react/hooks/use-translation-loader.ts (5)
packages/translations/src/react/index.ts (2)
useTranslationLoader(12-12)useTranslationContext(10-10)packages/translations/src/core/types.ts (2)
TranslationDictionary(3-3)TranslateFunction(7-10)packages/translations/src/react/hooks/use-translation-context.ts (1)
useTranslationContext(10-18)examples/translations/next-app/lib/translations.ts (1)
load(9-15)packages/translations/src/core/translate.ts (1)
createTranslate(10-35)
examples/translations/react/src/components/locale-switcher.tsx (2)
packages/translations/src/react/hooks/use-locale.ts (1)
useLocale(5-8)packages/translations/src/react/index.ts (1)
useLocale(11-11)
examples/translations/react/src/app.tsx (5)
examples/translations/next-app/lib/translations.ts (1)
load(9-15)packages/translations/src/vite/loader.ts (1)
createViteLoader(13-35)examples/translations/next-pages/pages/_app.tsx (1)
App(5-19)packages/translations/src/react/contexts/translation-context.tsx (1)
TranslationProvider(28-55)examples/translations/react/src/components/demo.tsx (1)
Demo(3-14)
packages/translations/src/react/hooks/use-locale.ts (2)
packages/translations/src/react/index.ts (2)
useLocale(11-11)useTranslationContext(10-10)packages/translations/src/react/hooks/use-translation-context.ts (1)
useTranslationContext(10-18)
packages/translations/src/core/translate.ts (1)
packages/translations/src/core/types.ts (3)
TranslationDictionary(3-3)TranslateFunction(7-10)TranslationVariables(5-5)
examples/translations/next-app/app/[lang]/page.tsx (5)
examples/translations/next-pages/pages/index.tsx (1)
Home(6-34)examples/translations/next-app/lib/translations.ts (1)
getTranslation(20-26)packages/translations/src/core/loader.ts (1)
getTranslation(14-22)packages/translations/src/next/index.ts (1)
Link(14-14)packages/translations/src/next/link.tsx (1)
Link(14-57)
packages/translations/src/next/provider.tsx (5)
packages/translations/src/next/index.ts (1)
NextTranslationProvider(3-3)packages/translations/src/core/types.ts (1)
TranslationLoader(12-15)examples/translations/next-app/lib/translations.ts (3)
locales(6-6)defaultLocale(7-7)load(9-15)packages/translations/src/react/contexts/translation-context.tsx (1)
TranslationContext(16-18)packages/translations/src/react/index.ts (1)
TranslationContext(3-3)
packages/translations/src/vite/loader.ts (1)
packages/translations/src/core/types.ts (2)
TranslationLoader(12-15)TranslationDictionary(3-3)
examples/translations/next-app/app/[lang]/layout.tsx (2)
examples/translations/next-app/lib/translations.ts (1)
locales(6-6)examples/translations/next-app/app/[lang]/providers.tsx (1)
Providers(8-20)
packages/translations/src/react/contexts/translation-context.tsx (2)
packages/translations/src/core/types.ts (1)
TranslationLoader(12-15)examples/translations/next-app/lib/translations.ts (2)
locales(6-6)load(9-15)
examples/translations/next-pages/pages/_app.tsx (2)
packages/translations/src/react/contexts/translation-context.tsx (1)
TranslationProvider(28-55)packages/translations/src/react/index.ts (1)
TranslationProvider(4-4)
examples/translations/next-app/middleware.ts (1)
examples/translations/next-app/lib/translations.ts (2)
locales(6-6)defaultLocale(7-7)
examples/translations/next-app/lib/translations.ts (2)
packages/translations/src/core/types.ts (1)
TranslationLoader(12-15)packages/translations/src/core/loader.ts (1)
getTranslation(14-22)
examples/translations/next-app/next.config.ts (1)
examples/translations/next-pages/next.config.js (1)
nextConfig(2-7)
examples/translations/next-app/app/[lang]/providers.tsx (3)
packages/translations/src/next/app-provider.tsx (1)
AppTranslationProvider(22-54)packages/translations/src/next/index.ts (1)
AppTranslationProvider(9-9)examples/translations/next-app/lib/translations.ts (2)
locales(6-6)load(9-15)
🔇 Additional comments (41)
examples/translations/react/src/locales/fr/common.json (1)
1-10: LGTM!The French translation file is well-structured with proper interpolation placeholders and plural forms. The French punctuation (space before "!") is grammatically correct.
examples/translations/react/src/locales/en/common.json (1)
1-10: LGTM!The English translation file is well-structured with clear interpolation placeholders and plural forms. The structure matches the French locale file, ensuring consistency across translations.
pnpm-workspace.yaml (1)
4-4: LGTM!Adding
"examples/**/*"appropriately extends the workspace to include the new translation examples. The glob pattern will recursively include all packages under the examples directory, which aligns well with the monorepo structure.Based on learnings, this change properly integrates with the existing Turborepo and pnpm workspaces configuration.
examples/translations/react/tsconfig.json (1)
1-21: LGTM!The TypeScript configuration is well-configured for a modern Vite + React project with appropriate compiler options:
- Bundler module resolution for Vite compatibility
- React JSX runtime for React 19
- Strict type-checking enabled
- Proper isolation and build settings
examples/translations/next-pages/locales/en/common.json (1)
1-18: LGTM!The translation file is comprehensive and well-organized, with proper interpolation placeholders and plural forms. French locale file (
fr/common.json) has matching keys and structure..github/workflows/checks.yml (1)
16-16: The commit hash34e114876b0b11c390a56381ad16ebd13914f8d5is legitimate and corresponds to v4.3.1 and the v4 floating tag in the official actions/checkout repository. The hash-pinning is secure.examples/translations/next-pages/next.config.js (1)
1-9: LGTM!The i18n configuration is correctly set up for Next.js Pages Router with "en" and "fr" locales matching the translation files added in this PR. The JSDoc annotation provides helpful IDE support.
apps/web/package.json (1)
26-26: Dependency updates appear safe for this codebase's usage patterns.While lucide-react v0.553.0 introduced a breaking icon rename (Fingerprint → FingerprintPattern) and v0.562.0 added an exports map, the codebase only uses public exports and does not import the affected Fingerprint icon, so these breaking changes won't cause issues. The @vercel/og update from 0.6.4 to 0.8.0 lacks public changelog documentation, but its usage is minimal (single import of ImageResponse in one file). These version bumps remain unrelated to the i18n feature and could be better addressed in a separate dependency-focused PR.
packages/translations/src/vite/index.ts (1)
1-1: LGTM!Standard barrel export pattern for exposing the Vite loader functionality.
examples/translations/next-app/tsconfig.json (1)
1-40: LGTM!The TypeScript configuration is properly set up for Next.js 15 with React 19, including the modern JSX transform and appropriate compiler options.
packages/translations/README.md (1)
1-285: LGTM!Comprehensive documentation with accurate examples for Next.js App Router (with proper async params handling per Next.js 15), Pages Router, and Vite. The examples correctly demonstrate the library's usage across different frameworks.
packages/translations/src/react/hooks/use-locale.ts (1)
1-8: LGTM!Clean implementation of the locale hook that appropriately delegates to the translation context and returns only locale-related properties.
packages/translations/src/react/hooks/use-translation-context.ts (1)
1-18: LGTM!Standard context hook pattern with appropriate error handling for usage outside the provider.
examples/translations/react/vite.config.ts (1)
1-6: LGTM!Standard Vite configuration for a React project.
examples/translations/next-pages/pages/_app.tsx (1)
1-19: LGTM with security consideration noted.The implementation correctly wraps the app in
TranslationProviderand handles missing translation files gracefully. The dynamicrequirepattern with template literals is safe here assuming the library validateslocaleandnsparameters to prevent path traversal attacks.packages/translations/src/core/index.ts (1)
1-3: LGTM! Clean barrel exports.The core module exports are well-organized, providing a clean public API surface for the translation utilities.
examples/translations/next-app/locales/en/common.json (1)
1-18: LGTM! Well-structured translation file.The English locale file has proper JSON structure with clear key organization and correct interpolation syntax for variables and plurals.
packages/translations/src/core/loader.ts (1)
1-22: LGTM! Clean loader implementation with helpful documentation.The
getTranslationutility is well-designed for server-side or static translation usage, with clear type signatures and a helpful JSDoc example demonstrating server component usage.examples/translations/react/src/components/locale-switcher.tsx (1)
3-14: LGTM! Simple and effective locale switcher.The component correctly uses the
useLocalehook to toggle between locales and provides clear visual feedback to users.examples/translations/next-pages/tsconfig.json (1)
1-35: LGTM! Standard Next.js TypeScript configuration.The TypeScript configuration is properly set up for Next.js Pages Router with appropriate compiler options, path aliases, and file inclusions.
examples/translations/next-app/app/[lang]/about/page.tsx (1)
1-41: LGTM! Proper App Router implementation with translations.The page component correctly uses the
"use client"directive for hook usage, integrates translation hooks properly, and demonstrates locale switching with the custom Link component. The implementation follows Next.js App Router patterns correctly.packages/translations/tsconfig.json (1)
6-6: TypeScript version requirement formoduleResolution: "bundler"is satisfied.The
packages/translations/package.jsoncorrectly specifies"typescript": "^5"in devDependencies, which meets the TypeScript 5.0+ requirement for thebundlermodule resolution strategy.examples/translations/next-app/app/[lang]/providers.tsx (1)
1-20: LGTM! Clean client-side provider wrapper.The Providers component correctly wraps
AppTranslationProviderwith the necessary props. ThedefaultLocaleprop is intentionally omitted since it defaults tolocales[0]in the provider implementation.examples/translations/next-app/app/[lang]/page.tsx (1)
5-10: LGTM! Correctly uses Next.js 15 async params pattern.The component properly types
paramsasPromise<{ lang: string }>and awaits it, which aligns with Next.js 15's async request APIs.examples/translations/react/src/app.tsx (1)
7-8: LGTM! Correct usage of Vite's import.meta.glob.The eager import of locale files and creation of the Vite loader is properly implemented for Vite's module system.
packages/translations/src/react/hooks/use-translation.ts (1)
11-14: LGTM! Clean and focused hook implementation.The hook provides a simple, intuitive API by delegating to
useTranslationLoaderwith a sensible default namespace.packages/translations/src/react/hooks/use-translation-loader.ts (1)
18-24: LGTM! Proper fallback logic for translation loading.The hook correctly attempts to load translations for the requested locale and falls back to the default locale when necessary. The conditional check ensures fallback only happens when locales differ.
packages/translations/src/vite/loader.ts (1)
13-35: LGTM! Well-documented Vite loader factory.The implementation correctly handles both ESM default exports and direct exports from Vite's
import.meta.glob. The function includes helpful JSDoc with usage examples.examples/translations/next-app/next.config.ts (1)
1-5: LGTM!Empty config is appropriate for the App Router approach to i18n. Unlike the Pages Router (which requires
i18nconfig innext.config.js), the App Router handles internationalization via route segments ([lang]) and middleware, which this example correctly implements.examples/translations/next-app/app/[lang]/layout.tsx (1)
7-9: LGTM!Correct usage of
generateStaticParamsto pre-render pages for each supported locale at build time.packages/translations/src/core/translate.ts (1)
10-35: LGTM!Clean implementation with proper handling of:
- Nested key resolution via dot notation
- Variable interpolation with
{name}syntax- Graceful fallback to the original key when translation is missing
- Type-safe traversal using
unknownand type guardspackages/translations/src/next/index.ts (1)
1-14: LGTM!Clean barrel file organization with clear separation between Pages Router, App Router, and component exports. The aliasing of
NextTranslationProvidertoTranslationProviderprovides a convenient API for Pages Router users.examples/translations/next-app/lib/translations.ts (1)
20-26: LGTM!The
getTranslationhelper correctly handles the Next.js 15 async params pattern, making it convenient for server components to retrieve translations.packages/translations/tsup.config.ts (1)
5-41: LGTM on theaddUseClientpost-processing logic.The function correctly:
- Processes both subdirectories (
react/,next/) and shared chunks- Handles both
.jsand.cjsextensions- Avoids duplicate directive injection by checking existing content
- Silently handles missing directories (appropriate for optional outputs)
packages/translations/src/react/index.ts (1)
1-16: LGTM!Clean barrel file with well-organized exports. Good use of
typekeyword for type-only exports, which helps with tree-shaking and build optimization.packages/translations/src/react/contexts/translation-context.tsx (1)
38-54: LGTM!The context value is properly memoized with all dependencies correctly listed. The provider structure follows React best practices.
packages/translations/src/next/app-provider.tsx (2)
33-53: LGTM!The memoization correctly includes all dependencies (
pathname,router) needed for thesetLocalecallback. The provider properly reusesTranslationContextfrom the base React implementation, maintaining consistency across the library.
38-42: ThesetLocaleimplementation is correctly designed for the intended routing structure. The provider is explicitly designed to work with Next.js App Router's[lang]dynamic segment (as noted in the code comment "Locale is passed from route params [lang]"), which guarantees that all pathnames will have the locale atsegments[1]. The provided examples confirm this usage pattern: the provider is instantiated within[lang]/layout.tsx, ensuring all routes follow the/[lang]/...structure. The edge cases mentioned (root path "/" or paths without locale prefix) do not occur in this design.Likely an incorrect or invalid review comment.
packages/translations/package.json (2)
8-29: LGTM!Well-structured conditional exports following modern Node.js ESM conventions. Each sub-path correctly defines
types,import, andrequirefields in the proper order for TypeScript and bundler compatibility.
52-53: No issues identified. The Next.js version^16.1.1and React version^19.2.3are appropriate for December 2025, and the package.json structure is correct.packages/translations/src/core/types.ts (1)
1-21: LGTM!Clean and minimal type definitions that properly model the translation system. The recursive
TranslationValuetype correctly handles nested translation structures, and the overall type design provides good type safety for consumers.
| }, | ||
| "nav": { | ||
| "home": "Accueil", | ||
| "about": "A propos" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use proper French accent: "À propos".
The phrase should be spelled "À propos" (with capital À and accent) rather than "A propos" to follow proper French orthography.
🔎 Suggested correction
"nav": {
"home": "Accueil",
- "about": "A propos"
+ "about": "À propos"
},
"about": {
- "title": "A propos",
+ "title": "À propos",
"description": "Ceci est une demo de @onruntime/translations avec Next.js App Router."
}Also applies to: 15-15
🤖 Prompt for AI Agents
In examples/translations/next-app/locales/fr/common.json around lines 12 and 15,
the French string "A propos" is missing the proper accent and capitalization;
replace it with "À propos" at both occurrences to follow correct French
orthography (ensure the JSON quotation and punctuation remain valid).
| }, | ||
| "nav": { | ||
| "home": "Accueil", | ||
| "about": "A propos" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use proper French accent: "À propos".
The phrase should be spelled "À propos" (with capital À and accent) rather than "A propos" to follow proper French orthography.
🔎 Suggested correction
"nav": {
"home": "Accueil",
- "about": "A propos"
+ "about": "À propos"
},
"about": {
- "title": "A propos",
+ "title": "À propos",
"description": "Ceci est une demo de @onruntime/translations avec Next.js Pages Router."
}Also applies to: 15-15
🤖 Prompt for AI Agents
In examples/translations/next-pages/locales/fr/common.json around lines 12 and
15, the translation uses "A propos" without the proper French accent; update
both entries to "À propos" (capital À with grave accent) to correct orthography.
| { | ||
| "name": "@onruntime/translations-example-react", | ||
| "version": "0.1.0", | ||
| "private": true, | ||
| "type": "module", | ||
| "scripts": { | ||
| "dev": "vite", | ||
| "build": "vite build", | ||
| "preview": "vite preview" | ||
| }, | ||
| "dependencies": { | ||
| "@onruntime/translations": "workspace:*", | ||
| "react": "^19.0.0", | ||
| "react-dom": "^19.0.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/react": "^19", | ||
| "@types/react-dom": "^19", | ||
| "@vitejs/plugin-react": "^4.3.0", | ||
| "typescript": "^5", | ||
| "vite": "^6.0.0" | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
React 19 Vite 6 @vitejs/plugin-react compatibility
💡 Result:
Short answer: Yes — React 19 is compatible with Vite 6 when you use the official React plugin (@vitejs/plugin-react or the SWC variant). Use the current @vitejs/plugin-react that matches your Vite 6 install (or @vitejs/plugin-react-oxc when using rolldown/rolldown-vite) and update related tooling (Vitest, templates) as needed. Examples and docs show Vite templates and plugin docs already targeting modern React 19 + Vite 6 usage. [1][2][3]
Notes/actions:
- Install/update to Vite 6 and the latest @vitejs/plugin-react (or @vitejs/plugin-react-swc / @vitejs/plugin-react-oxc for rolldown) and test your app. [1][2]
- No special JSX runtime changes are generally required — the plugin handles automatic JSX runtime and HMR. [1][3]
Sources:
[1] Vite plugins docs (@vitejs/plugin-react).
[2] Rolldown / Vite docs (rolldown integration & @vitejs/plugin-react-oxc).
[3] Upgrade writeup: Vitest 3, Vite 6 and React 19 (practical experience).
🌐 Web query:
@vitejs/plugin-react 4.3.0 React 19 support
💡 Result:
Short answer: @vitejs/plugin-react v4.3.0 introduced a breaking change for the React compiler (missing ./compiler-runtime specifier / incorrect assumption about runtime), so it can break React 19 compiler setups. The issue was fixed in v4.3.1 and later — upgrade to >=4.3.1 (or the latest 4.3.x) to get React Compiler / React 19 support working. [1][2]
Sources:
- GitHub issue reporting break in 4.3.0. [1]
- plugin-react changelog / release notes (4.3.1 fix and later 4.3.x compiler-related fixes). [2]
🌐 Web query:
React 19 release date stable version 2025
💡 Result:
React 19 stable was released on December 5, 2024. [1][2]
For context: patch/minor releases followed in 2025 (e.g., 19.1 on March 28, 2025). [2][3]
Sources:
[1] React blog — "React v19" (Dec 05, 2024).
[2] React release history/version log (React 19.0 first release date).
[3] Version listing showing 19.1 (Mar 28, 2025).
Use @vitejs/plugin-react@^4.3.1 or later — version 4.3.0 has a breaking change that breaks React Compiler support in React 19.
While React 19 and Vite 6 are compatible, @vitejs/plugin-react v4.3.0 introduced an issue with the React Compiler runtime that was fixed in v4.3.1. Update the dependency to "@vitejs/plugin-react": "^4.3.1" to ensure React 19 Compiler support works correctly. The rest of the setup (React 19, Vite 6, TypeScript 5) uses current stable versions and is properly configured.
🤖 Prompt for AI Agents
examples/translations/react/package.json lines 1-23: the project pins
@vitejs/plugin-react to 4.3.0 which breaks React 19 Compiler support; update the
dependency version to ^4.3.1 (or later) in package.json, then run your package
manager (npm/yarn/pnpm) to install updated deps and verify the dev/build/preview
scripts work with React 19.
| export default defineConfig([ | ||
| // Main entry - framework agnostic (types, core functions, server) | ||
| { | ||
| entry: { | ||
| index: "src/index.ts", | ||
| }, | ||
| format: ["esm", "cjs"], | ||
| dts: true, | ||
| clean: true, | ||
| splitting: false, | ||
| treeshake: true, | ||
| }, | ||
| // Client bundles (react, next) - with "use client" and splitting | ||
| { | ||
| entry: { | ||
| "react/index": "src/react/index.ts", | ||
| "next/index": "src/next/index.ts", | ||
| }, | ||
| format: ["esm", "cjs"], | ||
| dts: true, | ||
| external: ["react", "react-dom", "next", "@onruntime/translations"], | ||
| splitting: true, | ||
| treeshake: true, | ||
| onSuccess: addUseClient, | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat packages/translations/package.json | jq '.scripts'Repository: onRuntime/onruntime
Length of output: 141
🏁 Script executed:
cat packages/translations/tsup.config.tsRepository: onRuntime/onruntime
Length of output: 2197
🏁 Script executed:
# Check tsup version to understand behavior
cat packages/translations/package.json | jq '.devDependencies.tsup // .dependencies.tsup'Repository: onRuntime/onruntime
Length of output: 67
🌐 Web query:
tsup array config parallel sequential build v8
💡 Result:
Short answer: tsup accepts an array config (multiple build configs) and runs those builds in parallel by default; to force package/config ordering you must run builds sequentially yourself (npm scripts, CI steps, separate tsup invocations) or use a helper plugin such as @jhae/tsup-sequential-build-plugin which coordinates sequencing while still allowing parallel format builds. See tsup repo/docs and the plugin/readme for details. [1][2][3]
Sources
- tsup GitHub (project and docs; array config / multiple builds supported; project notes about maintenance). [1]
- tsup npm (v8.x info). [2]
- @jhae/tsup-sequential-build-plugin (plugin that provides sequential ordering across tsup array configs while preserving parallel format builds). [3]
🌐 Web query:
tsup clean: true behavior multiple configs same output directory
💡 Result:
Short answer: tsup's clean (clean: true) removes the entire output directory before writing. If you run multiple configs or outputs that share the same outDir and each run/entry also performs a clean, later runs can delete files produced by earlier runs. To avoid this, only clean once (e.g., set clean: true for the first config/run and false for the rest), or use separate output directories or an explicit pre-build cleaning script. Sources: tsup repo/docs and related issues plus rollup's cleanDir guidance showing the same behavior. [1][2][3]
Sources:
- tsup GitHub / docs. [1]
- Rollup cleanDir docs (explanation for “clean the output dir once when multiple outputs/configs share dir”). [2]
- tsup issues showing problems with multiple entrypoints/configs and clean behavior. [3]
Potential race condition with clean: true and parallel builds.
tsup v8 runs array configs in parallel by default. The clean: true on the main entry removes the entire output directory, and since all three configs write to the same dist folder, the first config's clean operation could delete files being written by the react/next and vite configs simultaneously.
While you're fortunate that only the first config has clean: true, this still creates a race condition that could intermittently cause build failures. Consider one of these solutions:
- Only set
clean: trueonce (e.g., move it to the first client bundle config which has multiple entries and runs after the main entry would complete) - Use a prebuild script (
"prebuild": "rimraf dist"in package.json) to clean once before any tsup config runs - Use a sequential build plugin if you need explicit ordering guarantees
🤖 Prompt for AI Agents
In packages/translations/tsup.config.ts around lines 43 to 67, the config uses
clean: true in an array-based tsup setup which runs configs in parallel and can
cause a race where one config wipes the shared dist while others are writing;
remove clean: true from all tsup configs in this file and instead add a single
prebuild step in package.json (e.g., use rimraf or similar) to clean dist once
before tsup runs, or alternatively keep clean: true on exactly one config that
you explicitly run first via a sequential build; ensure only one cleaning action
runs before parallel builds start.
Summary by CodeRabbit
Release Notes
New Features
Documentation
Chores
✏️ Tip: You can customize this high-level summary in your review settings.