Skip to content

Conversation

@kazupon
Copy link
Member

@kazupon kazupon commented Nov 15, 2025

Description

Linked Issues

resolve #19

Additional context

Summary by CodeRabbit

  • New Features

    • Plugin-based H3 i18n integration, new lightweight server examples, and a public plugin options type.
    • Added multiple locale utilities (header, query, path, cookie helpers).
  • Documentation

    • Updated docs and examples to H3-centric usage, plugin workflows, and new locale helpers.
  • Tests

    • Expanded and restructured E2E/integration tests to verify plugin-based flows and new utilities.

@coderabbitai
Copy link

coderabbitai bot commented Nov 15, 2025

Warning

Rate limit exceeded

@kazupon has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 2 minutes and 24 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 11a122b and cd4c720.

📒 Files selected for processing (3)
  • packages/h3/README.md (11 hunks)
  • packages/h3/docs/functions/detectLocaleFromAcceptLanguageHeader.md (3 hunks)
  • packages/h3/src/index.ts (8 hunks)

Walkthrough

The PR migrates the packages/h3 package to H3 v2: replaces middleware-based initialization with a plugin-based API and srvx server runner, introduces symbol-keyed event context fields, adds/exports a plugin and I18nPluginOptions, adapts defineI18nMiddleware to return onRequest/onResponse hooks, updates docs/playgrounds/tests to H3 class + srvx usage, and adjusts workspace/devDependencies (including knip.config.ts change).

Changes

Cohort / File(s) Summary
Workspace & monorepo config
knip.config.ts, pnpm-workspace.yaml
Added ignoreDependencies: ['srvx'] to knip workspace config and reordered/updated workspace catalog; added msw to minimumReleaseAgeExclude.
Package devDependencies
packages/h3/package.json
Bumped h3 to ^2.0.1-rc.5, removed supertest/@types/supertest, added srvx devDependency.
Core implementation
packages/h3/src/index.ts, packages/h3/src/symbols.ts
Added SYMBOL_I18N and SYMBOL_I18N_LOCALE; switched event context storage to symbols; added/exported I18nPluginOptions type and plugin (definePlugin); updated defineI18nMiddleware to return { onRequest, onResponse }; changed event access to use event.req.
Public API typings / interfaces
packages/h3/docs/interfaces/I18nMiddleware.md, packages/h3/docs/type-aliases/I18nPluginOptions.md
I18nMiddleware docs updated to use generic Middleware type, removed onAfterResponse in favor of onResponse; added I18nPluginOptions alias docs.
Documentation & index
packages/h3/README.md, packages/h3/docs/index.md, packages/h3/docs/variables/plugin.md
Large docs refresh to H3 v2 terminology and plugin API; added plugin variable docs; expanded functions list (getHeaderLocale, getQueryLocale, try*/setCookieLocale, etc.) and updated examples.
Docs removal / function docs
packages/h3/docs/functions/defineI18nMiddleware.md, packages/h3/docs/functions/detectLocaleFromAcceptLanguageHeader.md, packages/h3/docs/functions/useTranslation.md
Removed legacy defineI18nMiddleware docs; updated detectLocaleFromAcceptLanguageHeader and useTranslation examples to H3 class and plugin/onRequest/onResponse patterns.
Playgrounds — migrated to H3 v2 + srvx
packages/h3/playground/basic/index.ts, packages/h3/playground/global-schema/index.ts, packages/h3/playground/local-schema/index.ts, packages/h3/playground/typesafe-schema/index.ts
Replaced createApp/router/middleware flows with new H3({ plugins: [...] }), route handlers via app.get/app.use, and srvx server initialization; adjust defineI18nMiddleware usage to return onRequest/onResponse where applicable.
Playgrounds — new examples
packages/h3/playground/util-header/index.ts, packages/h3/playground/util-query/index.ts
Added minimal H3 v2 examples demonstrating getHeaderLocale and getQueryLocale with srvx-based server.
Tests — e2e / integration / unit updates
packages/h3/spec/e2e.spec.ts, packages/h3/spec/integration.spec.ts, packages/h3/src/index.test.ts, packages/h3/src/index.test-d.ts
Split e2e tests into focused cases; integration tests migrated from supertest to H3 + plugin + app.fetch requests; updated mocks/assertions to symbol-based context and onResponse hook; relaxed some type assertions.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client
    participant H3App as H3 v2 App
    participant I18nPlugin as I18n Plugin (onRequest/onResponse)
    participant Route as Route Handler
    participant Context as Event Context (symbols)

    Client->>H3App: HTTP Request
    H3App->>I18nPlugin: onRequest(event)
    I18nPlugin->>Context: set SYMBOL_I18N, SYMBOL_I18N_LOCALE on event.context
    H3App->>Route: invoke route handler (event)
    Route->>Context: useTranslation reads/writes SYMBOL_I18N / SYMBOL_I18N_LOCALE
    Route-->>Client: response (translated)
    H3App->>I18nPlugin: onResponse(event, response)
    I18nPlugin-->>Client: finalize/modify response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas needing close review:
    • packages/h3/src/index.ts — plugin wiring, symbol-based context migration, public API changes.
    • packages/h3/spec/integration.spec.ts and packages/h3/spec/e2e.spec.ts — test semantics after switch from supertest to fetch and srvx.
    • Playgrounds (packages/h3/playground/*) — ensure examples match exported plugin/defineI18nMiddleware signatures.
    • packages/h3/package.json and knip.config.ts — dependency/devDependency and workspace-ignore changes affecting CI.

Possibly related issues

  • #19: Support h3 v2 — This PR migrates the codebase to H3 v2 (constructor/plugin API, Middleware typing, srvx usage, symbol-backed event context), addressing the objectives of H3 v2 support.
  • intlify/h3#65 — Changes correspond to H3 v2 migration and plugin/srvx adoption described in that issue.

Poem

🐰
I nibble symbols in the night,
Plugins bloom and routes take flight,
Srvx hums and H3 hops high,
Translations whisper, banners fly,
A little rabbit cheers, "Nice try!"

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(h3): support h3 v2' accurately and concisely summarizes the main objective: adding support for h3 version 2.
Linked Issues check ✅ Passed The PR successfully addresses h3 v2 compatibility by updating package.json to h3 v2.0.1-rc.5, migrating from middleware-based patterns to plugin-based architecture, and updating documentation and examples throughout.
Out of Scope Changes check ✅ Passed All changes are directly related to h3 v2 support: dependency updates, API migration (middleware to plugins), documentation updates, example refactoring, and test modifications. No unrelated changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@kazupon kazupon added feature Includes new features H3 labels Nov 15, 2025
@kazupon kazupon mentioned this pull request Nov 15, 2025
@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 15, 2025

Open in StackBlitz

npm i https://pkg.pr.new/@intlify/h3@32
npm i https://pkg.pr.new/@intlify/hono@32

commit: cd4c720

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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/h3/README.md (1)

114-140: Fix Nitro plugin example to use correct middleware export name.

The README example at lines 123 and 138 destructures onAfterResponse, but the middleware exports onResponse (per H3 v2 changes). The Nitro hook name 'afterResponse' is correct, but the callback variable should be onResponse:

const { onRequest, onResponse } = defineI18nMiddleware({
  // ...
})

nitroApp.hooks.hook('request', onRequest)
nitroApp.hooks.hook('afterResponse', onResponse)
packages/h3/src/index.ts (1)

213-235: Fix JSDoc example name for detectLocaleFromAcceptLanguageHeader

The JSDoc example still references detectLocaleWithAcceeptLanguageHeader (with typos in both the verb “From” vs “With” and “Accept” spelling), but the exported function is detectLocaleFromAcceptLanguageHeader.

To avoid confusion for users following the docs, I’d align the example with the actual export name, e.g.:

- * import { defineI18nMiddleware, detectLocaleWithAcceeptLanguageHeader } from '@intlify/h3'
+ * import {
+ *   defineI18nMiddleware,
+ *   detectLocaleFromAcceptLanguageHeader
+ * } from '@intlify/h3'
...
- *   locale: detectLocaleWithAcceeptLanguageHeader
+ *   locale: detectLocaleFromAcceptLanguageHeader

The implementation itself (getHeaderLocale(event.req).toString()) looks correct for H3 v2’s H3Event.

Also applies to: 241-242

🧹 Nitpick comments (6)
packages/h3/playground/util-header/index.ts (1)

8-9: Add error handling for locale detection.

The code assumes getHeaderLocale always returns a valid locale. Consider adding error handling for cases where the locale header is missing or invalid.

Apply this diff to add basic error handling:

 app.get('/', event => {
   const locale = getHeaderLocale(event.req)
-  return locale.toString()
+  return locale?.toString() ?? 'en'
 })
packages/h3/playground/util-query/index.ts (1)

8-9: Add error handling for locale detection.

Similar to util-header, the code assumes getQueryLocale always returns a valid locale. Consider adding error handling for cases where the locale query parameter is missing or invalid.

Apply this diff to add basic error handling:

 app.get('/', event => {
   const locale = getQueryLocale(event.req)
-  return locale.toString()
+  return locale?.toString() ?? 'en'
 })
packages/h3/spec/e2e.spec.ts (1)

31-59: Consider adding server readiness check instead of fixed delay.

The tests use a fixed 1000ms delay for server startup, which could be flaky in slow CI environments or unnecessarily slow in fast ones.

Consider implementing a retry-based readiness check:

const waitForServer = async (port: number, maxRetries = 10, delayMs = 100) => {
  for (let i = 0; i < maxRetries; i++) {
    try {
      await runCommand(`curl --fail http://localhost:${port}/health || exit 0`)
      return
    } catch {
      await delay(delayMs)
    }
  }
  throw new Error(`Server not ready after ${maxRetries * delayMs}ms`)
}

Alternatively, if the playground servers support it, parse stdout for a "ready" message instead of a fixed timeout.

packages/h3/spec/integration.spec.ts (1)

91-152: Verify async locale detector behavior with useTranslation and bound functions

The async and async parallel tests correctly model async locale detectors that:

  • Derive the locale via getQueryLocale(event.req), and
  • Lazily populate i18n.messages[locale] through dynamic imports before returning the locale.

However, in defineI18nMiddleware (in src/index.ts), locale detectors are stored via getLocaleDetector(event, i18n), which uses .bind(...) on the original locale function. useTranslation then checks localeDetector.constructor.name === 'AsyncFunction' to decide whether to await it.

In JavaScript, bound functions typically report constructor.name === 'Function' even if the original was an async function, so an async detector wrapped with .bind may no longer be detectable via this heuristic. If that happens, the async branch in useTranslation won’t run, and these tests would be relying on implementation quirks rather than a guaranteed behavior.

Please verify in your runtime that:

  • A detector declared as async (event: H3Event, i18n: CoreContext<...>) => {...} and then wrapped by getLocaleDetector is still recognized as async by useTranslation, or
  • Adjust useTranslation/defineI18nMiddleware to track “async-ness” before binding (or always await the detector result) so async detectors behave predictably.

Also applies to: 153-218

packages/h3/src/index.ts (2)

183-197: Optional: clear locale detector from context on response for symmetry

defineI18nMiddleware correctly:

  • Creates a shared CoreContext,
  • Stores a request-specific locale detector in event.context[SYMBOL_I18N_LOCALE],
  • Assigns i18n.locale and event.context[SYMBOL_I18N] on request, and
  • Resets i18n.locale and deletes event.context[SYMBOL_I18N] on response.

For completeness and to keep per-request state fully cleaned up, you might also delete event.context[SYMBOL_I18N_LOCALE] in the onResponse hook:

-    onResponse: onResponse((_, event) => {
-      i18n.locale = orgLocale
-      delete event.context[SYMBOL_I18N]
-    })
+    onResponse: onResponse((_, event) => {
+      i18n.locale = orgLocale
+      delete event.context[SYMBOL_I18N]
+      delete event.context[SYMBOL_I18N_LOCALE]
+    })

This doesn’t affect correctness in typical H3 lifecycles but keeps the context shape tidy.

Also applies to: 199-209


404-407: Error message could mention plugin-based setup

The error thrown when middleware is not initialized currently says:

“please setup onRequest and onResponse options of H3 with the middleware obtained with defineI18nMiddleware

Now that you expose a plugin for the common path, it might be clearer to mention both options, e.g., “either register the @intlify/h3 plugin on your H3 instance or wire defineI18nMiddleware’s onRequest/onResponse hooks manually.”

This is just a wording tweak but will better guide users toward the preferred plugin-based setup.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9be549 and 024e6e0.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (24)
  • knip.config.ts (1 hunks)
  • packages/h3/README.md (9 hunks)
  • packages/h3/docs/functions/defineI18nMiddleware.md (0 hunks)
  • packages/h3/docs/functions/detectLocaleFromAcceptLanguageHeader.md (3 hunks)
  • packages/h3/docs/functions/useTranslation.md (3 hunks)
  • packages/h3/docs/index.md (2 hunks)
  • packages/h3/docs/interfaces/DefineLocaleMessage.md (0 hunks)
  • packages/h3/docs/interfaces/I18nMiddleware.md (1 hunks)
  • packages/h3/docs/type-aliases/I18nPluginOptions.md (1 hunks)
  • packages/h3/docs/variables/plugin.md (1 hunks)
  • packages/h3/package.json (1 hunks)
  • packages/h3/playground/basic/index.ts (1 hunks)
  • packages/h3/playground/global-schema/index.ts (2 hunks)
  • packages/h3/playground/local-schema/index.ts (1 hunks)
  • packages/h3/playground/typesafe-schema/index.ts (3 hunks)
  • packages/h3/playground/util-header/index.ts (1 hunks)
  • packages/h3/playground/util-query/index.ts (1 hunks)
  • packages/h3/spec/e2e.spec.ts (2 hunks)
  • packages/h3/spec/integration.spec.ts (7 hunks)
  • packages/h3/src/index.test-d.ts (1 hunks)
  • packages/h3/src/index.test.ts (5 hunks)
  • packages/h3/src/index.ts (8 hunks)
  • packages/h3/src/symbols.ts (1 hunks)
  • pnpm-workspace.yaml (2 hunks)
💤 Files with no reviewable changes (2)
  • packages/h3/docs/interfaces/DefineLocaleMessage.md
  • packages/h3/docs/functions/defineI18nMiddleware.md
🧰 Additional context used
🧬 Code graph analysis (9)
packages/h3/spec/integration.spec.ts (1)
packages/h3/src/index.ts (2)
  • detectLocaleFromAcceptLanguageHeader (241-242)
  • getQueryLocale (30-30)
packages/h3/playground/util-header/index.ts (1)
packages/h3/src/index.ts (1)
  • getHeaderLocale (27-27)
packages/h3/playground/global-schema/index.ts (1)
packages/h3/src/index.ts (1)
  • detectLocaleFromAcceptLanguageHeader (241-242)
packages/h3/src/index.test.ts (1)
packages/h3/src/symbols.ts (2)
  • SYMBOL_I18N (10-10)
  • SYMBOL_I18N_LOCALE (11-11)
packages/h3/playground/util-query/index.ts (1)
packages/h3/src/index.ts (1)
  • getQueryLocale (30-30)
packages/h3/playground/basic/index.ts (1)
packages/h3/src/index.ts (1)
  • detectLocaleFromAcceptLanguageHeader (241-242)
packages/h3/playground/typesafe-schema/index.ts (1)
packages/h3/src/index.ts (1)
  • defineI18nMiddleware (170-210)
packages/h3/playground/local-schema/index.ts (1)
packages/h3/src/index.ts (1)
  • detectLocaleFromAcceptLanguageHeader (241-242)
packages/h3/src/index.ts (2)
packages/h3/src/symbols.ts (2)
  • SYMBOL_I18N (10-10)
  • SYMBOL_I18N_LOCALE (11-11)
packages/hono/src/index.ts (5)
  • CoreContext (31-31)
  • defineI18nMiddleware (120-159)
  • detectLocaleFromAcceptLanguageHeader (188-189)
  • getHeaderLocale (19-19)
  • DefineLocaleMessage (83-83)
🔇 Additional comments (28)
packages/h3/src/index.test-d.ts (1)

35-37: The simplified eventMock is sufficient for type testing.

The .test-d.ts file performs compile-time type checking only (not runtime execution). The mock with { context: {} } as H3Event correctly satisfies the type requirements because vitest's expectTypeOf assertions verify that the return value of useTranslation() is correctly inferred as string. The symbol-keyed properties (SYMBOL_I18N and SYMBOL_I18N_LOCALE) are optional context extensions and are not needed for type inference verification. Runtime behavior is tested separately in index.test.ts with a more complete mock setup.

packages/h3/playground/typesafe-schema/index.ts (3)

2-2: LGTM! Correct h3 v2 import.

The import change from createApp to { H3 } correctly reflects the h3 v2 API update.


13-13: LGTM! Correct destructuring pattern.

The destructuring of { onRequest, onResponse } correctly matches the updated defineI18nMiddleware API that returns separate request and response handlers.


23-25: Code is correct and follows documented h3 conventions. No changes needed.

The middleware registration at lines 24-25 is verified to be correct. The h3 source code explicitly documents that onRequest must be registered before onResponse, and the playground follows this pattern exactly. This matches the documented approach in the h3 library itself (see packages/h3/src/index.ts lines 160-161). Other playgrounds don't register middleware because they demonstrate different h3 features (plugins, utilities) rather than middleware handling—this is expected variance, not an inconsistency.

packages/h3/docs/variables/plugin.md (1)

1-48: LGTM!

The plugin documentation is clear and provides a comprehensive example demonstrating the new plugin-based architecture for H3 v2.

knip.config.ts (1)

6-7: LGTM!

Adding srvx to ignoreDependencies is appropriate since it's used in playground files (already excluded via the ignore pattern).

pnpm-workspace.yaml (2)

5-8: LGTM!

Catalog reordering improves alphabetical consistency with no functional impact.


21-21: Adding msw to built dependencies.

The addition of msw to onlyBuiltDependencies suggests it requires native compilation. This is correct if the project uses msw for mocking in tests.

packages/h3/docs/functions/useTranslation.md (1)

30-30: LGTM!

Documentation updates correctly reflect the H3 v2 plugin-based architecture and consistent capitalization of "H3" throughout.

Also applies to: 43-43, 66-66, 71-77

packages/h3/src/symbols.ts (1)

1-11: LGTM!

Using unique symbols for context keys is an excellent pattern that provides type safety and prevents key collisions. The implementation is clean and well-documented.

packages/h3/package.json (2)

73-73: srvx dependency version is current.

The latest published version of srvx is 0.9.6 (published Nov 14, 2025), which matches the version specified in the package.json. The caret syntax allows for compatible updates and is appropriate for this dependency.


71-71: Confirm intentional use of h3 v2.0.1-rc.5 over stable v1.15.4.

The latest stable h3 release is v1.15.4, but this package specifies v2.0.1-rc.5 (release candidate). No stable v2 release exists yet. Please confirm this RC version is intentional and document any known compatibility or stability considerations for your use case.

packages/h3/docs/type-aliases/I18nPluginOptions.md (1)

1-21: LGTM! Documentation is clear and well-structured.

The new I18nPluginOptions type alias documentation correctly describes the type parameters and their defaults. The format is consistent with typical API documentation standards.

packages/h3/docs/functions/detectLocaleFromAcceptLanguageHeader.md (1)

30-47: LGTM! Documentation updated to reflect H3 v2 API.

The example code correctly demonstrates:

  • Import of H3 constructor instead of createApp
  • Plugin-based middleware initialization with onRequest and onResponse hooks
  • Proper chaining with .use() methods

The changes align with the broader migration to h3 v2's plugin-based architecture.

packages/h3/src/index.test.ts (2)

16-22: LGTM! Event mock updated for H3 v2.

The event mock structure correctly reflects the H3 v2 API with req.headers.get for header access. This is more aligned with the Web Request API pattern.


58-71: LGTM! Symbol-based context access implemented correctly.

The test correctly demonstrates:

  • Symbol-based context keys (SYMBOL_I18N, SYMBOL_I18N_LOCALE) for storing i18n state
  • Proper binding of locale detector to the event context
  • Updated event mock structure consistent with H3 v2

This approach improves encapsulation by preventing naming conflicts in the event context.

packages/h3/spec/e2e.spec.ts (1)

27-29: LGTM! Better test isolation with afterEach.

Switching from afterAll to afterEach ensures each test has a clean server state, which is the correct approach for isolated e2e tests. This prevents test interference and makes failures easier to diagnose.

packages/h3/playground/global-schema/index.ts (1)

21-43: LGTM! Clean migration to H3 v2 plugin-based architecture.

The playground correctly demonstrates:

  • H3 constructor with plugins array for dependency injection
  • Plugin-based i18n configuration with locale detection and messages
  • Direct route definition with app.get instead of separate router
  • Modern server startup with serve({ fetch: app.fetch }) and await server.ready()

This is a significant improvement over the middleware-based approach and aligns well with h3 v2's design patterns.

packages/h3/docs/interfaces/I18nMiddleware.md (1)

9-16: LGTM! Interface documentation updated for H3 v2.

The documentation correctly reflects the API changes:

  • Updated to reference H3 (capitalized)
  • onRequest and onResponse hooks properly documented with links to h3.dev
  • Removal of onAfterResponse aligns with h3 v2's standardized hook naming
packages/h3/playground/local-schema/index.ts (1)

24-30: LGTM! Demonstrates local schema pattern effectively.

This playground correctly shows an alternative to global type declarations:

  • Inline ResourceSchema type definition within the route handler
  • Type parameter passed to useTranslation<ResourceSchema>(event)
  • Enables type-safe translation keys without global module augmentation

This is a valid pattern for applications that prefer localized type scoping.

packages/h3/README.md (3)

7-12: LGTM! Clear communication about H3 v2 migration.

The README properly:

  • Uses consistent "H3" capitalization throughout
  • Clearly notes this is for H3 v2 with a link to the H3 v1 repository
  • Sets appropriate expectations for users

44-75: LGTM! Excellent usage examples for utility functions.

The new examples clearly demonstrate:

  • Direct locale detection from headers with getHeaderLocale(event.req)
  • Query-based locale detection with getQueryLocale(event.req)
  • Clean H3 constructor and route definition patterns

These examples provide clear, actionable guidance for common use cases.


79-112: LGTM! Comprehensive translation example with plugin pattern.

The example effectively demonstrates:

  • H3 constructor with plugins array
  • Plugin-based i18n configuration
  • useTranslation usage in route handlers
  • Complete working server setup with node:http

This provides a complete, copy-pasteable example for users migrating to h3 v2.

packages/h3/docs/index.md (1)

9-14: Docs accurately reflect the new plugin-based API

The added Variables, Functions, Interfaces, and Type Aliases entries are consistent with the exported plugin, locale helpers, I18nMiddleware, and I18nPluginOptions in src/index.ts. I don’t see inconsistencies or broken links here.

Also applies to: 19-33, 40-40, 47-47

packages/h3/spec/integration.spec.ts (2)

21-53: Translation test coverage for Accept-Language looks solid

The translation test now exercises the plugin-based setup (new H3({ plugins: [i18n(...)] }), app.get, and app.fetch with an Accept-Language header) and validates the expected English message. This is a good smoke test for the default header-based locale detection path.


55-89: Custom query-based locale detector test is well wired

The localeDetector that calls getQueryLocale(event.req).toString() and the corresponding plugin configuration verify that a synchronous detector using query parameters behaves as expected. This nicely validates the public getQueryLocale helper together with the plugin.

packages/h3/src/index.ts (2)

20-22: Symbol-based H3 context augmentation is appropriate

Using SYMBOL_I18N and SYMBOL_I18N_LOCALE on H3EventContext is a good way to avoid property name collisions and keeps the i18n state internal to this package. The module augmentation for 'h3' and the imports from ./symbols.ts look consistent and type-safe.

Also applies to: 56-63


72-83: Plugin surface and middleware types look aligned; confirm against h3 v2 typings

The updated I18nMiddleware (with Middleware-typed onRequest/onResponse), the I18nPluginOptions alias, and the plugin export that wires defineI18nMiddleware into definePlugin all match the intended H3 v2 plugin model (new H3({ plugins: [plugin] }) and h3.use(...)).

To be safe, please double-check against the exact h3@2.x typings that:

  • onRequest/onResponse from 'h3' indeed return something assignable to your Middleware type, and
  • h3.use(onRequest/onResponse) is the recommended way to register these hooks for plugins.

Also applies to: 86-97, 125-129

Comment on lines 249 to 270
import { defineI18nMiddleware } from '@intlify/h3'
import { createApp } from 'h3'
import { H3 } from 'h3'
import en from './locales/en.ts'

// define resource schema, as 'en' is master resource schema
type ResourceSchema = typeof en

const middleware = defineI18nMiddleware<[ResourceSchema], 'en' | 'ja'>({
const i18nMiddleware = defineI18nMiddleware<[ResourceSchema], 'en' | 'ja'>({
messages: {
en: { hello: 'Hello, {name}' }
}
// something options
// ...
})

const app = createApp({ ...middleware })
const app = new H3()
app.use(i18nMiddleware.onRequest)
app.use(i18nMiddleware.onResponse)

// something your implementation code ...
// ...
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Inconsistency: Example still uses old middleware pattern.

The type-safe resources example hasn't been fully migrated to the H3 v2 plugin-based pattern. It still shows:

  • defineI18nMiddleware (old API)
  • app.use(i18nMiddleware.onRequest) pattern
  • new H3() without plugins array

Update the example to align with the H3 v2 plugin pattern shown in other sections:

-const i18nMiddleware = defineI18nMiddleware<[ResourceSchema], 'en' | 'ja'>({
-  messages: {
-    en: { hello: 'Hello, {name}' }
-  }
-  // something options
-  // ...
-})
-
-const app = new H3()
-app.use(i18nMiddleware.onRequest)
-app.use(i18nMiddleware.onResponse)
+const app = new H3({
+  plugins: [
+    i18n<[ResourceSchema], 'en' | 'ja'>({
+      messages: {
+        en: { hello: 'Hello, {name}' },
+        ja: { hello: 'こんにちは、{name}' }
+      }
+      // something options
+      // ...
+    })
+  ]
+})

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/h3/README.md around lines 249 to 270, the example uses the old
middleware API (defineI18nMiddleware, new H3(), app.use(...)) which is
inconsistent with H3 v2 plugin-based pattern; replace it with the plugin pattern
by importing/creating the i18n plugin (type-safe resources preserved),
instantiate H3 with a plugins array that includes the i18n plugin, and remove
any app.use(i18nMiddleware.onRequest/onResponse) calls; ensure types for
ResourceSchema and supported locales remain and show registering messages via
the plugin options.

Comment on lines +49 to +51
expectTypeOf<string>(t('test')).toExtend<string>()
expectTypeOf<string>(t('foo')).toExtend<string>()
expectTypeOf<string>(t('bar.buz.baz')).toExtend<string>()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Incorrect type assertion syntax - not testing the intended behavior.

The current syntax expectTypeOf<string>(t('test')).toExtend<string>() places the generic <string> on expectTypeOf, which means it's checking if string extends string (a tautology) rather than verifying that t('test') actually returns a string type.

Apply this diff to fix the type assertions:

-  expectTypeOf<string>(t('test')).toExtend<string>()
-  expectTypeOf<string>(t('foo')).toExtend<string>()
-  expectTypeOf<string>(t('bar.buz.baz')).toExtend<string>()
+  expectTypeOf(t('test')).toMatchTypeOf<string>()
+  expectTypeOf(t('foo')).toMatchTypeOf<string>()
+  expectTypeOf(t('bar.buz.baz')).toMatchTypeOf<string>()

Alternatively, use the more concise .toBeString() matcher:

-  expectTypeOf<string>(t('test')).toExtend<string>()
-  expectTypeOf<string>(t('foo')).toExtend<string>()
-  expectTypeOf<string>(t('bar.buz.baz')).toExtend<string>()
+  expectTypeOf(t('test')).toBeString()
+  expectTypeOf(t('foo')).toBeString()
+  expectTypeOf(t('bar.buz.baz')).toBeString()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expectTypeOf<string>(t('test')).toExtend<string>()
expectTypeOf<string>(t('foo')).toExtend<string>()
expectTypeOf<string>(t('bar.buz.baz')).toExtend<string>()
expectTypeOf(t('test')).toMatchTypeOf<string>()
expectTypeOf(t('foo')).toMatchTypeOf<string>()
expectTypeOf(t('bar.buz.baz')).toMatchTypeOf<string>()
🤖 Prompt for AI Agents
In packages/h3/src/index.test-d.ts around lines 49 to 51, the type assertions
incorrectly place the generic on expectTypeOf (making the checks tautological).
Update the assertions to assert the type of the call result instead — e.g. call
expectTypeOf(t('test')) and either use .toEqualTypeOf<string>() /
.toExtendTypeOf<string>() with the generic on the matcher or simply use
.toBeString() to assert the returned type is string for t('test'), t('foo') and
t('bar.buz.baz').

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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/h3/src/index.ts (1)

212-243: Fix JSDoc example name for detectLocaleFromAcceptLanguageHeader

The example imports and uses detectLocaleWithAcceeptLanguageHeader, but the actual export is detectLocaleFromAcceptLanguageHeader, and “Acceept” is misspelled. This could confuse users copying the snippet.

Suggested change:

- * import { defineI18nMiddleware, detectLocaleWithAcceeptLanguageHeader } from '@intlify/h3'
+ * import { defineI18nMiddleware, detectLocaleFromAcceptLanguageHeader } from '@intlify/h3'
...
- *   locale: detectLocaleWithAcceeptLanguageHeader
+ *   locale: detectLocaleFromAcceptLanguageHeader
packages/h3/playground/global-schema/index.ts (1)

16-19: Fix package name typo in global schema comment

The guidance comment uses @intlifly/h3 instead of @intlify/h3, which could trip up users copying it.

Suggested change:

-  // please use `declare module '@intlifly/h3'`, if you want to use global resource schema in your project.
+  // please use `declare module '@intlify/h3'`, if you want to use global resource schema in your project.
♻️ Duplicate comments (2)
packages/h3/README.md (2)

66-66: Fix typo in import statement.

This is a duplicate of earlier feedback. The package name should be @intlify/h3 not @inltify/h3.

-import { getQueryLocale } from '@inltify/h3'
+import { getQueryLocale } from '@intlify/h3'

249-270: Example still uses old middleware pattern instead of H3 v2 plugin pattern.

This is a duplicate of earlier feedback. The type-safe resources example should be updated to use the H3 v2 plugin-based pattern for consistency with other examples in the document (see lines 79–112).

The example should migrate from:

const i18nMiddleware = defineI18nMiddleware<[ResourceSchema], 'en' | 'ja'>({...})
const app = new H3()
app.use(i18nMiddleware.onRequest)
app.use(i18nMiddleware.onResponse)

To:

const app = new H3({
  plugins: [
    i18n<[ResourceSchema], 'en' | 'ja'>({...})
  ]
})
🧹 Nitpick comments (3)
packages/h3/README.md (2)

232-232: Fix typo: "exeample" → "example".

User-facing documentation has duplicate typo.

-  > The exeample code is [here]
+  > The example code is [here]

Also applies to: 318-318


311-311: Fix typo: "twe" → "two".

-resource keys completion has twe ways.
+resource keys completion has two ways.
packages/h3/src/index.ts (1)

20-22: Plugin + middleware wiring looks solid; optionally clear locale detector on response

The new I18nMiddleware shape, I18nPluginOptions, and plugin export align well with an H3 v2-style plugin: definePlugin receives options, and h3.use(onRequest/onResponse) wires the symbol-based context correctly via defineI18nMiddleware.

One small cleanup you might consider: onResponse resets i18n.locale and deletes SYMBOL_I18N, but leaves SYMBOL_I18N_LOCALE on the event context. Even though events are short-lived, deleting both keeps per-request state symmetrical and avoids dangling references to user-defined detectors.

Possible tweak:

   onResponse: onResponse((_, event) => {
     i18n.locale = orgLocale
-    delete event.context[SYMBOL_I18N]
+    delete event.context[SYMBOL_I18N]
+    delete event.context[SYMBOL_I18N_LOCALE]
   })

Please double‑check this still matches the expectations of the H3 plugin API and your tests around context cleanup.

Also applies to: 56-62, 71-83, 92-129, 170-209

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8502970 and 11a122b.

📒 Files selected for processing (8)
  • packages/h3/README.md (9 hunks)
  • packages/h3/playground/basic/index.ts (1 hunks)
  • packages/h3/playground/global-schema/index.ts (2 hunks)
  • packages/h3/playground/local-schema/index.ts (1 hunks)
  • packages/h3/playground/typesafe-schema/index.ts (3 hunks)
  • packages/h3/playground/util-header/index.ts (1 hunks)
  • packages/h3/playground/util-query/index.ts (1 hunks)
  • packages/h3/src/index.ts (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/h3/playground/util-query/index.ts
  • packages/h3/playground/util-header/index.ts
  • packages/h3/playground/typesafe-schema/index.ts
🧰 Additional context used
🧬 Code graph analysis (4)
packages/h3/playground/global-schema/index.ts (1)
packages/h3/src/index.ts (1)
  • detectLocaleFromAcceptLanguageHeader (241-242)
packages/h3/playground/basic/index.ts (1)
packages/h3/src/index.ts (1)
  • detectLocaleFromAcceptLanguageHeader (241-242)
packages/h3/playground/local-schema/index.ts (1)
packages/h3/src/index.ts (1)
  • detectLocaleFromAcceptLanguageHeader (241-242)
packages/h3/src/index.ts (2)
packages/h3/src/symbols.ts (2)
  • SYMBOL_I18N (10-10)
  • SYMBOL_I18N_LOCALE (11-11)
packages/hono/src/index.ts (4)
  • CoreContext (31-31)
  • defineI18nMiddleware (120-159)
  • detectLocaleFromAcceptLanguageHeader (188-189)
  • getHeaderLocale (19-19)
🔇 Additional comments (3)
packages/h3/src/index.ts (1)

383-407: Async locale detection handling in useTranslation is now robust

Switching useTranslation to:

  • Read event.context[SYMBOL_I18N] / SYMBOL_I18N_LOCALE, and
  • Always await localeDetector(event),

removes the brittle constructor.name === 'AsyncFunction' check and correctly supports both sync and async detectors (await on a non‑promise is a no‑op). Updating the error message to reference onRequest/onResponse setup via defineI18nMiddleware also matches the new API.

This addresses the prior concern about async detectors being misdetected after bind and looks good.

Please re‑run the integration/e2e tests that cover both sync and async locale detectors to confirm behavior remains correct across your supported H3 versions.

Also applies to: 410-415, 420-427

packages/h3/playground/local-schema/index.ts (1)

1-37: Local-schema playground wiring matches the new plugin API

The example (new H3({ plugins: [i18n(...)] }), app.get with useTranslation<ResourceSchema>, and serve({ port, fetch: app.fetch })) is consistent with the updated H3 + i18n plugin design and looks correct.

Please run this playground against your current h3, srvx, and @intlify/h3 versions to ensure the server boots and translations resolve as expected.

packages/h3/playground/basic/index.ts (1)

1-35: Basic playground: plugin-based H3 + i18n example looks correct

This snippet cleanly demonstrates the intended H3 v2 usage: plugin-based i18n configuration, app.get with useTranslation, and serving via srvx. The inline comment correctly references @intlify/h3.

As a sanity check, please run this playground to confirm startup and translation behavior with your current dependency versions.

@kazupon kazupon merged commit bbd7dbd into main Nov 15, 2025
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Includes new features H3

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support h3 v2

2 participants