Skip to content

Conversation

@antoinekm
Copy link
Member

@antoinekm antoinekm commented Dec 28, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced new internationalization library supporting React, Next.js (both App and Pages Router), and Vite
    • Added example applications demonstrating locale switching, pluralization, and multi-language support
    • Included middleware for Next.js locale-based routing
  • Documentation

    • Added comprehensive README with setup and usage examples for all supported frameworks
  • Chores

    • Updated external dependencies
    • Updated GitHub Actions workflow configuration
    • Expanded workspace configuration to include examples

✏️ Tip: You can customize this high-level summary in your review settings.

Copilot AI review requested due to automatic review settings December 28, 2025 05:22
@vercel
Copy link

vercel bot commented Dec 28, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
onruntime-website Ready Ready Preview, Comment Dec 28, 2025 5:32am

@coderabbitai
Copy link

coderabbitai bot commented Dec 28, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit 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.

📝 Walkthrough

Walkthrough

This PR introduces @onruntime/translations, a comprehensive internationalization library with framework-specific adapters for React, Next.js App/Pages Router, and Vite. The library includes core translation utilities, context providers, hooks, and locale routing capabilities, accompanied by three complete example applications demonstrating usage patterns. Supporting changes include a GitHub Actions workflow update, dependency version bumps, and workspace configuration expansion.

Changes

Cohort / File(s) Summary
GitHub Actions & Dependencies
.github/workflows/checks.yml, apps/web/package.json
Updated GitHub Actions checkout action SHA; bumped @vercel/og (^0.6.4 → ^0.8.0) and lucide-react (^0.515.0 → ^0.562.0) versions.
Core Translation Library
packages/translations/src/core/*, packages/translations/src/index.ts
Introduced core translation types (TranslationValue, TranslationDictionary, TranslateFunction, TranslationLoader, TranslationConfig, TranslationVariables), utility functions (createTranslate, getTranslation), and central index re-exports.
React Adapter
packages/translations/src/react/*
Implemented TranslationContext, TranslationProvider component, and hooks (useTranslation, useTranslationContext, useLocale, useTranslationLoader) for React applications.
Next.js Adapter (App Router)
packages/translations/src/next/app-provider.tsx
Added AppTranslationProvider component for Next.js App Router with locale validation, URL rewriting via useRouter, and context memoization.
Next.js Adapter (Pages Router & Link)
packages/translations/src/next/provider.tsx, packages/translations/src/next/link.tsx, packages/translations/src/next/index.ts
Implemented NextTranslationProvider for Pages Router, Link wrapper component with automatic locale-prefixing for internal/external hrefs, and consolidated exports.
Vite Loader
packages/translations/src/vite/*
Added createViteLoader utility to construct TranslationLoader from import.meta.glob results, supporting both default and direct exports.
Package Configuration
packages/translations/package.json, packages/translations/tsconfig.json, packages/translations/tsup.config.ts
Defined package exports (main, ./react, ./next, ./vite entries), TypeScript compiler options targeting ES2020, and multi-entry tsup build with "use client" directive injection for client bundles.
Documentation
packages/translations/README.md
Added comprehensive README covering installation, usage scenarios for React/Next.js/Vite, setup examples, translation file structure, and MIT license.
Next.js App Router Example
examples/translations/next-app/app/*, examples/translations/next-app/lib/translations.ts, examples/translations/next-app/middleware.ts, examples/translations/next-app/locales/*/common.json, examples/translations/next-app/package.json, examples/translations/next-app/next.config.ts, examples/translations/next-app/tsconfig.json
Complete example application with dynamic routes, middleware locale detection, generateStaticParams for SSG, Providers wrapper, layout and page components using translation hooks, English/French locale files, and project configuration.
Next.js Pages Router Example
examples/translations/next-pages/pages/*, examples/translations/next-pages/locales/*/common.json, examples/translations/next-pages/next.config.js, examples/translations/next-pages/package.json, examples/translations/next-pages/tsconfig.json
Complete example with Pages Router, custom App component wrapping with TranslationProvider, index and about pages using useTranslation hook, Next.js i18n config with en/fr locales, and project dependencies.
React Example
examples/translations/react/src/*, examples/translations/react/locales/*/common.json, examples/translations/react/index.html, examples/translations/react/package.json, examples/translations/react/tsconfig.json, examples/translations/react/vite.config.ts
Complete Vite-based React example with TranslationProvider using createViteLoader, Demo and LocaleSwitcher components, pluralization support, translation files for en/fr, and development configuration.
Workspace Configuration
pnpm-workspace.yaml
Expanded workspace to include all nested files under examples directory via "examples/**/*" pattern.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Hop through the translations so bright,
Contexts and hooks guide the way just right,
React and Next.js now speak many tongues,
Locale by locale, the i18n is sung!
From English to French, the words take their flight.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch antoinekm/i18n

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7bad3c3 and e3984a1.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (53)
  • .github/workflows/checks.yml
  • apps/web/package.json
  • examples/translations/next-app/app/[lang]/about/page.tsx
  • examples/translations/next-app/app/[lang]/layout.tsx
  • examples/translations/next-app/app/[lang]/page.tsx
  • examples/translations/next-app/app/[lang]/providers.tsx
  • examples/translations/next-app/lib/translations.ts
  • examples/translations/next-app/locales/en/common.json
  • examples/translations/next-app/locales/fr/common.json
  • examples/translations/next-app/middleware.ts
  • examples/translations/next-app/next.config.ts
  • examples/translations/next-app/package.json
  • examples/translations/next-app/tsconfig.json
  • examples/translations/next-pages/locales/en/common.json
  • examples/translations/next-pages/locales/fr/common.json
  • examples/translations/next-pages/next.config.js
  • examples/translations/next-pages/package.json
  • examples/translations/next-pages/pages/_app.tsx
  • examples/translations/next-pages/pages/about.tsx
  • examples/translations/next-pages/pages/index.tsx
  • examples/translations/next-pages/tsconfig.json
  • examples/translations/react/index.html
  • examples/translations/react/package.json
  • examples/translations/react/src/app.tsx
  • examples/translations/react/src/components/demo.tsx
  • examples/translations/react/src/components/locale-switcher.tsx
  • examples/translations/react/src/locales/en/common.json
  • examples/translations/react/src/locales/fr/common.json
  • examples/translations/react/src/main.tsx
  • examples/translations/react/tsconfig.json
  • examples/translations/react/vite.config.ts
  • packages/translations/README.md
  • packages/translations/package.json
  • packages/translations/src/core/index.ts
  • packages/translations/src/core/loader.ts
  • packages/translations/src/core/translate.ts
  • packages/translations/src/core/types.ts
  • packages/translations/src/index.ts
  • packages/translations/src/next/app-provider.tsx
  • packages/translations/src/next/index.ts
  • packages/translations/src/next/link.tsx
  • packages/translations/src/next/provider.tsx
  • packages/translations/src/react/contexts/translation-context.tsx
  • packages/translations/src/react/hooks/use-locale.ts
  • packages/translations/src/react/hooks/use-translation-context.ts
  • packages/translations/src/react/hooks/use-translation-loader.ts
  • packages/translations/src/react/hooks/use-translation.ts
  • packages/translations/src/react/index.ts
  • packages/translations/src/vite/index.ts
  • packages/translations/src/vite/loader.ts
  • packages/translations/tsconfig.json
  • packages/translations/tsup.config.ts
  • pnpm-workspace.yaml

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

Copy link
Contributor

Copilot AI left a 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/translations package 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];
Copy link

Copilot AI Dec 28, 2025

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.

Copilot uses AI. Check for mistakes.
return (
<div style={{ fontFamily: "system-ui", padding: "2rem" }}>
<h1>{t("greeting", { name: "John" })}</h1>
<p>{t(count === 1 ? "messages.one" : "messages.other", { count })}</p>
Copy link

Copilot AI Dec 28, 2025

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.

Suggested change
<p>{t(count === 1 ? "messages.one" : "messages.other", { count })}</p>
<p>{t("messages.other", { count })}</p>

Copilot uses AI. Check for mistakes.
@antoinekm antoinekm merged commit 6a12796 into master Dec 28, 2025
3 checks passed
@antoinekm antoinekm deleted the antoinekm/i18n branch December 28, 2025 05:33
Copy link

@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: 4

♻️ Duplicate comments (6)
examples/translations/react/src/components/demo.tsx (1)

10-10: Condition is always false.

Since count is hardcoded to 5 on line 5, the condition count === 1 will always evaluate to false, making the ternary expression always select "messages.other". Consider either:

  • Simplifying to directly use "messages.other" if demonstrating plural form
  • Making count dynamic to demonstrate both singular and plural cases
examples/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 useRouter import 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 useTranslation hook 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 === 1 condition on line 21 is always false since count is 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 empty locales array.

If locales is an empty array and defaultLocale is not provided, locales[0] will be undefined, causing the locale state to initialize as undefined. 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 http and // prefixes. Other protocols like mailto:, 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 corresponding index.html does 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 of router dependency in useMemo.

Including the router object in the useMemo dependency 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 params pattern for Next.js 15 and properly sets the lang attribute. 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 replace could 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 _next and favicon.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 TranslationLoader type works well with require() but limits usage with dynamic import() 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7bad3c3 and 5f88cf5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (53)
  • .github/workflows/checks.yml
  • apps/web/package.json
  • examples/translations/next-app/app/[lang]/about/page.tsx
  • examples/translations/next-app/app/[lang]/layout.tsx
  • examples/translations/next-app/app/[lang]/page.tsx
  • examples/translations/next-app/app/[lang]/providers.tsx
  • examples/translations/next-app/lib/translations.ts
  • examples/translations/next-app/locales/en/common.json
  • examples/translations/next-app/locales/fr/common.json
  • examples/translations/next-app/middleware.ts
  • examples/translations/next-app/next.config.ts
  • examples/translations/next-app/package.json
  • examples/translations/next-app/tsconfig.json
  • examples/translations/next-pages/locales/en/common.json
  • examples/translations/next-pages/locales/fr/common.json
  • examples/translations/next-pages/next.config.js
  • examples/translations/next-pages/package.json
  • examples/translations/next-pages/pages/_app.tsx
  • examples/translations/next-pages/pages/about.tsx
  • examples/translations/next-pages/pages/index.tsx
  • examples/translations/next-pages/tsconfig.json
  • examples/translations/react/index.html
  • examples/translations/react/package.json
  • examples/translations/react/src/app.tsx
  • examples/translations/react/src/components/demo.tsx
  • examples/translations/react/src/components/locale-switcher.tsx
  • examples/translations/react/src/locales/en/common.json
  • examples/translations/react/src/locales/fr/common.json
  • examples/translations/react/src/main.tsx
  • examples/translations/react/tsconfig.json
  • examples/translations/react/vite.config.ts
  • packages/translations/README.md
  • packages/translations/package.json
  • packages/translations/src/core/index.ts
  • packages/translations/src/core/loader.ts
  • packages/translations/src/core/translate.ts
  • packages/translations/src/core/types.ts
  • packages/translations/src/index.ts
  • packages/translations/src/next/app-provider.tsx
  • packages/translations/src/next/index.ts
  • packages/translations/src/next/link.tsx
  • packages/translations/src/next/provider.tsx
  • packages/translations/src/react/contexts/translation-context.tsx
  • packages/translations/src/react/hooks/use-locale.ts
  • packages/translations/src/react/hooks/use-translation-context.ts
  • packages/translations/src/react/hooks/use-translation-loader.ts
  • packages/translations/src/react/hooks/use-translation.ts
  • packages/translations/src/react/index.ts
  • packages/translations/src/vite/index.ts
  • packages/translations/src/vite/loader.ts
  • packages/translations/tsconfig.json
  • packages/translations/tsup.config.ts
  • pnpm-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.json
  • examples/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 hash 34e114876b0b11c390a56381ad16ebd13914f8d5 is 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 TranslationProvider and handles missing translation files gracefully. The dynamic require pattern with template literals is safe here assuming the library validates locale and ns parameters 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 getTranslation utility 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 useLocale hook 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 for moduleResolution: "bundler" is satisfied.

The packages/translations/package.json correctly specifies "typescript": "^5" in devDependencies, which meets the TypeScript 5.0+ requirement for the bundler module resolution strategy.

examples/translations/next-app/app/[lang]/providers.tsx (1)

1-20: LGTM! Clean client-side provider wrapper.

The Providers component correctly wraps AppTranslationProvider with the necessary props. The defaultLocale prop is intentionally omitted since it defaults to locales[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 params as Promise<{ 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 useTranslationLoader with 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 i18n config in next.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 generateStaticParams to 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 unknown and type guards
packages/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 NextTranslationProvider to TranslationProvider provides a convenient API for Pages Router users.

examples/translations/next-app/lib/translations.ts (1)

20-26: LGTM!

The getTranslation helper 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 the addUseClient post-processing logic.

The function correctly:

  • Processes both subdirectories (react/, next/) and shared chunks
  • Handles both .js and .cjs extensions
  • 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 type keyword 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 the setLocale callback. The provider properly reuses TranslationContext from the base React implementation, maintaining consistency across the library.


38-42: The setLocale implementation 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 at segments[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, and require fields in the proper order for TypeScript and bundler compatibility.


52-53: No issues identified. The Next.js version ^16.1.1 and React version ^19.2.3 are 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 TranslationValue type correctly handles nested translation structures, and the overall type design provides good type safety for consumers.

},
"nav": {
"home": "Accueil",
"about": "A propos"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +1 to +23
{
"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"
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 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.

Comment on lines +43 to +67
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,
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 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.ts

Repository: 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: true once (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.

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.

2 participants