@dts-stn/react-router-i18n-routes

中文文档
1.0.1发布于 2026/10/19MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 2909

Type-safe, translated (i18n) route definitions for React Router (framework mode).

原始使用文档
文档目录34 节
  1. Why?
  2. Features
  3. Contents
  4. Installation
  5. Requirements
  6. Entry points
  7. Quick start
  8. 1. Define the routes
  9. 2. Register the routes with React Router
  10. 3. Link, redirect, and switch languages
  11. Concepts
  12. Page routes
  13. Layout routes
  14. The routing instance
  15. Recipes
  16. Set <html lang> in the root layout
  17. Redirect / to the visitor's preferred language
  18. Show a 404 page in the right language
  19. Add hreflang alternate links for SEO
  20. Support three or more languages
  21. Reload the document when switching languages
  22. Build an app-specific link component
  23. Handle routing errors
  24. API reference
  25. @dts-stn/react-router-i18n-routes
  26. @dts-stn/react-router-i18n-routes/react
  27. @dts-stn/react-router-i18n-routes/route-config
  28. Behavior details
  29. Path matching
  30. Path generation
  31. Language detection
  32. FAQ
  33. Contributing
  34. License

@dts-stn/react-router-i18n-routes

npm version CI types: TypeScript license: MIT

Type-safe, translated URLs for React Router framework mode.

Declare every page once, with a path for each language. You get the React Router route config, localized links and redirects, language detection, and a language switcher. Every route file and language is a string literal type, so a broken link is a compile error, not a 404.

const i18nRoutes = defineI18nRoutes([
  {
    id: 'CASE-0001',
    file: 'routes/case.tsx',
    paths: { en: '/en/case/:caseId', fr: '/fr/dossier/:caseId' },
  },
]);

const i18nRouting = createI18nRouting(i18nRoutes);

i18nRouting.getPath('routes/case.tsx', 'fr', { params: { caseId: '42' } }); // '/fr/dossier/42'
i18nRouting.translatePath('/en/case/42', 'fr'); // '/fr/dossier/42'
i18nRouting.getPath('routes/case.tsx', 'de'); // ❌ type error: 'de' is not a supported language
i18nRouting.getPath('routes/cases.tsx', 'en'); // ❌ type error: no such route file

Why?

A /:lang/... prefix is enough when only the language segment changes. Many sites, including government sites bound by official-languages rules, need fully translated paths instead: /en/dashboard and /fr/tableau-de-bord. Doing that by hand means registering every page once per language, keeping the paths in sync, and repeating the mapping in every link, redirect, and language toggle. This package keeps one table of routes and builds everything else from it.

Features

  • 🗺️ One source of truth. Each page is declared once, and React Router gets one route per language.
  • 🔒 Fully typed, no as const. Route files, ids, and languages are inferred as literal unions. Unknown files, layout files, and unsupported languages are rejected at compile time.
  • 🔗 Links and redirects. <I18nLink> replaces <Link>, and redirect() builds Responses for loaders and actions, with params, search, and hash.
  • 🌐 Language switching. <I18nLanguageSwitchLink> links to the current page in another language and keeps its params, search, and hash.
  • 🧭 URL matching. URLs map back to routes and languages using React Router's own ranking, so static segments always beat dynamic ones.
  • 🗣️ Language negotiation. The language comes from the URL, with an optional Accept-Language fallback.
  • ✅ Fail-fast validation. Duplicate ids or files, missing translations, and route id collisions throw at startup.
  • 🪶 Small and tree-shakeable. ESM-only, sideEffects: false, no runtime dependencies beyond its peers, and it works with React 18 and 19 and React Router 7 and 8.

Contents

Installation

npm install @dts-stn/react-router-i18n-routes
# or
pnpm add @dts-stn/react-router-i18n-routes
# or
yarn add @dts-stn/react-router-i18n-routes

Requirements

PackageVersionNeeded for
react-router^7.0.0 || ^8.0.0everything
react>=18.0.0@dts-stn/react-router-i18n-routes/react
@react-router/dev^7.0.0 || ^8.0.0@dts-stn/react-router-i18n-routes/route-config

react and @react-router/dev are optional peers. Install them only if you use the entry point that needs them.

The package is ESM-only and requires Node.js 20 or later. Type inference relies on const type parameters, so TypeScript 5.0 or later is required.

Entry points

Import pathContentsUse from
@dts-stn/react-router-i18n-routesRoute definitions, createI18nRouting(), types, and errorsanywhere (client and server)
@dts-stn/react-router-i18n-routes/reactHooks and link componentsReact components
@dts-stn/react-router-i18n-routes/route-configConversion to React Router route configapp/routes.ts

Quick start

1. Define the routes

Keep the definitions in their own module so app/routes.ts, components, loaders, and actions can all import them.

// app/i18n-routes.ts
import type { I18nLanguageOf, I18nRouteFileOf } from '@dts-stn/react-router-i18n-routes';
import { createI18nRouting, defineI18nRoutes } from '@dts-stn/react-router-i18n-routes';

export const i18nRoutes = defineI18nRoutes([
  {
    id: 'HOME-0001',
    file: 'routes/index.tsx',
    paths: { en: '/en/', fr: '/fr/' },
  },
  {
    // A pathless layout route
    file: 'routes/protected/layout.tsx',
    children: [
      {
        id: 'DASH-0001',
        file: 'routes/protected/dashboard.tsx',
        paths: { en: '/en/dashboard', fr: '/fr/tableau-de-bord' },
      },
      {
        id: 'CASE-0001',
        file: 'routes/protected/case.tsx',
        paths: { en: '/en/case/:caseId', fr: '/fr/dossier/:caseId' },
      },
    ],
  },
]);

export const i18nRouting = createI18nRouting(i18nRoutes);

export type I18nRouteFile = I18nRouteFileOf<typeof i18nRoutes>;
// 'routes/index.tsx' | 'routes/protected/dashboard.tsx' | 'routes/protected/case.tsx'

export type Language = I18nLanguageOf<typeof i18nRoutes>;
// 'en' | 'fr'

2. Register the routes with React Router

// app/routes.ts
import type { RouteConfig } from '@react-router/dev/routes';
import { route } from '@react-router/dev/routes';

import { toRouteConfigEntries } from '@dts-stn/react-router-i18n-routes/route-config';

import { i18nRoutes } from './i18n-routes';

export default [
  route('/api/health', 'routes/api/health.ts'), // non-translated routes work as usual
  ...toRouteConfigEntries(i18nRoutes),
] satisfies RouteConfig;

[!IMPORTANT] React Router's config loader does not resolve path aliases such as ~/, so import the definitions into app/routes.ts with a relative path.

import {
  I18nLanguageSwitchLink,
  I18nLink,
  useIsCurrentRoute,
  useIsNavigatingTo,
  useLanguage,
} from '@dts-stn/react-router-i18n-routes/react';

import { i18nRouting } from '~/i18n-routes';

export function Navigation() {
  const { alternateLanguage } = useLanguage(i18nRouting);
  const isDashboard = useIsCurrentRoute(i18nRouting, 'routes/protected/dashboard.tsx');
  const isLoadingCase = useIsNavigatingTo(i18nRouting, 'routes/protected/case.tsx', { params: { caseId: '42' } });

  return (
    <nav>
      {/* Links to the current language by default */}
      <I18nLink routing={i18nRouting} file="routes/protected/dashboard.tsx" aria-current={isDashboard ? 'page' : undefined}>
        Dashboard
      </I18nLink>

      {/* Any React Router <Link> prop is forwarded */}
      <I18nLink
        routing={i18nRouting}
        file="routes/protected/case.tsx"
        params={{ caseId: '42' }}
        search="tab=notes"
        prefetch="intent"
      >
        Case 42 {isLoadingCase && '…'}
      </I18nLink>

      {/* The current page in the other language, with the same params, search, and hash */}
      <I18nLanguageSwitchLink routing={i18nRouting}>
        {alternateLanguage === 'fr' ? 'Français' : 'English'}
      </I18nLanguageSwitchLink>
    </nav>
  );
}
// app/routes/protected/layout.tsx
import { i18nRouting } from '~/i18n-routes';

import type { Route } from './+types/layout';

export function loader({ request }: Route.LoaderArgs) {
  if (!isSignedIn(request)) {
    const language = i18nRouting.getLanguage(request) ?? 'en';
    throw i18nRouting.redirect('routes/index.tsx', language);
  }
}

That's it. Every file, language, and params value above is type-checked against app/i18n-routes.ts.

Concepts

Page routes

A page route has an id, a route module file, and a path for each language:

{ id: 'DASH-0001', file: 'routes/protected/dashboard.tsx', paths: { en: '/en/dashboard', fr: '/fr/tableau-de-bord' } }
  • file is the route module, relative to the app directory, as in app/routes.ts. It is also the lookup key: links, redirects, and hooks refer to a page by its file.
  • paths uses React Router path syntax: dynamic segments (:caseId), optional segments (:tab?), and splats (*) all work. Every page route must declare the same set of languages.
  • id must be unique. React Router gets one route per language, with the id ${id}-${LANGUAGE} (DASH-0001-EN, DASH-0001-FR). Pass these ids to useRouteLoaderData(), or build them with getI18nRouteId().

Layout routes

A layout route has a file and children but no id or paths. It becomes a pathless React Router layout() route that renders its children through <Outlet />, which is useful for authentication guards, shared chrome, and shared loaders. Layouts can be nested. A layout cannot be a link target, so passing a layout file to getPath() is a type error.

The routing instance

createI18nRouting(i18nRoutes) validates the definitions once and returns an I18nRouting object with every lookup and URL builder. Create it once at module level and import it wherever it is needed. The React hooks and components take it as their first argument or routing prop.

Recipes

Set <html lang> in the root layout

// app/root.tsx
import type { ReactNode } from 'react';

import { Links, Meta, Scripts, ScrollRestoration } from 'react-router';

import { useCurrentLanguage } from '@dts-stn/react-router-i18n-routes/react';

import { i18nRouting } from '~/i18n-routes';

export function Layout({ children }: { children: ReactNode }) {
  const language = useCurrentLanguage(i18nRouting) ?? 'en';

  return (
    <html lang={language}>
      <head>
        <Meta />
        <Links />
      </head>
      <body>
        {children}
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

Redirect / to the visitor's preferred language

Register a plain index route next to the i18n routes, and negotiate the language from the Accept-Language header:

// app/routes.ts
export default [index('routes/language-redirect.ts'), ...toRouteConfigEntries(i18nRoutes)] satisfies RouteConfig;
// app/routes/language-redirect.ts
import { i18nRouting } from '~/i18n-routes';

import type { Route } from './+types/language-redirect';

export function loader({ request }: Route.LoaderArgs) {
  // The language of the URL path, then the Accept-Language header, then English
  const language = i18nRouting.getLanguage(request, { acceptLanguage: true }) ?? 'en';
  return i18nRouting.redirect('routes/index.tsx', language);
}

Show a 404 page in the right language

getLanguage() falls back to the first path segment when no page route matches, so a catch-all route still knows that /fr/page-inexistante is French:

// app/routes.ts
export default [...toRouteConfigEntries(i18nRoutes), route('*', 'routes/not-found.tsx')] satisfies RouteConfig;
// app/routes/not-found.tsx
export default function NotFound() {
  const language = useCurrentLanguage(i18nRouting) ?? 'en';
  return <h1>{language === 'fr' ? 'Page introuvable' : 'Page not found'}</h1>;
}
import { useLocation } from 'react-router';

import { i18nRouting } from '~/i18n-routes';

const ORIGIN = 'https://www.example.com';

export function AlternateLinks() {
  const { pathname } = useLocation();

  return i18nRouting.languages.map((language) => {
    const path = i18nRouting.translatePath(pathname, language);
    return path && <link key={language} rel="alternate" hrefLang={language} href={new URL(path, ORIGIN).href} />;
  });
}

Render <AlternateLinks /> inside <head> in app/root.tsx.

Support three or more languages

alternateLanguage is set only for bilingual apps. With more languages, list alternateLanguages and pass each one to I18nLanguageSwitchLink:

const LANGUAGE_NAMES = { en: 'English', fr: 'Français', es: 'Español' } as const;

export function LanguageMenu() {
  const { alternateLanguages } = useLanguage(i18nRouting);

  return (
    <ul>
      {alternateLanguages.map((language) => (
        <li key={language}>
          <I18nLanguageSwitchLink routing={i18nRouting} language={language}>
            {LANGUAGE_NAMES[language]}
          </I18nLanguageSwitchLink>
        </li>
      ))}
    </ul>
  );
}

I18nLanguageSwitchLink sets lang and hrefLang on the <a>, so screen readers pronounce each language name correctly.

Reload the document when switching languages

If translations are loaded once per document (for example, an i18next instance created on the server), pass reloadDocument so that switching languages does a full page load:

<I18nLanguageSwitchLink routing={i18nRouting} reloadDocument>
  Français
</I18nLanguageSwitchLink>

Bind routing and your default styles once:

import type { I18nLinkProps } from '@dts-stn/react-router-i18n-routes/react';
import { I18nLink } from '@dts-stn/react-router-i18n-routes/react';

import type { i18nRoutes } from '~/i18n-routes';
import { i18nRouting } from '~/i18n-routes';

export function AppLink(props: Omit<I18nLinkProps<typeof i18nRoutes>, 'routing'>) {
  return <I18nLink routing={i18nRouting} className="underline" {...props} />;
}
<AppLink file="routes/protected/dashboard.tsx">Dashboard</AppLink> // ✅
<AppLink file="routes/protected/dashbord.tsx">Dashboard</AppLink> // ❌ type error

For non-link navigation (navigate(), <Form action>, fetcher.submit()), use useI18nPath() to build the path.

Handle routing errors

Every error thrown by the package is an I18nRoutingError with a machine-readable code:

import { I18nRoutingError } from '@dts-stn/react-router-i18n-routes';

try {
  const { route, language, params } = i18nRouting.getRouteByPath(pathname);
} catch (error) {
  if (error instanceof I18nRoutingError && error.code === 'ROUTE_NOT_FOUND') {
    // ...
  }
  throw error;
}

The find* methods return undefined instead of throwing, which is often simpler.

API reference

@dts-stn/react-router-i18n-routes

defineI18nRoutes(routes)

Returns routes unchanged, typed as literals, with no as const needed. Use it to declare the route table.

createI18nRouting(routes)

Validates the routes and returns an I18nRouting instance. Throws I18nRoutingError with code INVALID_ROUTES if:

  • two page routes share an id or a file,
  • the page routes do not all declare the same languages, or
  • two route/language pairs generate the same React Router route id. For example, languages en and EN collide, and so do route PAGE with language fr-CA and route PAGE-FR with language ca, which both generate PAGE-FR-CA.

I18nRouting

MemberReturnsDescription
routesTThe route definitions.
languagesLanguage[]The supported languages, in declaration order.
getPath(file, language, options?)stringThe localized, root-relative path. options: params, search (string or URLSearchParams), hash.
redirect(file, language, options?)ResponseA redirect to getPath(). options.init sets the status (default 302) or a full ResponseInit.
translatePath(pathname, language, options?)string | undefinedThe matching page route's path in another language, keeping its params (/en/case/42 → /fr/dossier/42). options: search, hash.
getLanguage(resource, options?)Language | undefinedThe language of a pathname, URL, or Request. See language detection.
getAlternateLanguages(language)Language[]The other supported languages, in declaration order.
findRouteByPath(pathname){ route, language, params } | undefinedMatches a pathname to a page route and language.
getRouteByPath(pathname){ route, language, params }Same as findRouteByPath(), but throws ROUTE_NOT_FOUND.
findRouteByFile(file)PageRoute | undefinedThe page route for a file. Accepts any string, so you can test unknown values.
getRouteByFile(file)PageRouteThe page route for a known file, typed as that exact route. Throws ROUTE_NOT_FOUND.

getI18nRouteId(id, language)

The React Router route id for one language of a page route: getI18nRouteId('DASH-0001', 'fr') → 'DASH-0001-FR'.

negotiateLanguage(acceptLanguage, languages)

The best supported language for an Accept-Language header value, or undefined. See language detection.

negotiateLanguage('fr-CA,fr;q=0.9,en;q=0.8', ['en', 'fr']); // 'fr'

isI18nPageRoute(value), isI18nLayoutRoute(value)

Type guards for walking route definitions.

I18nRoutingError

The error thrown by the package. Its code is one of:

CodeThrown when
INVALID_ROUTEScreateI18nRouting() finds inconsistent definitions.
ROUTE_NOT_FOUNDNo page route matches the file or pathname passed to a get* method.
LANGUAGE_NOT_DETERMINEDuseI18nPath() or I18nLink gets no language and the current URL has none.

Types

TypeDescription
I18nRoute<L>, I18nPageRoute<L>, I18nLayoutRoute<L>Route definition types, generic over the language union L.
I18nPaths<L>{ [language]: path }.
I18nRouteFileOf<T>The union of page route files in typeof routes.
I18nRouteIdOf<T>The union of page route ids.
I18nLanguageOf<T>The union of languages declared by every page route.
I18nPageRouteOf<T>The union of page route definitions.
I18nPageRouteForFile<T, F>The page route for file F (the return type of getRouteByFile()).
I18nRouting<T>, I18nRouteMatch<T>The routing instance and the result of findRouteByPath().
I18nPathOptions, I18nRedirectOptions, I18nTranslatePathOptions, I18nLanguageOptionsOption types for the I18nRouting methods.
I18nRoutingErrorCodeThe union of error codes.

The type helpers also accept wide types such as I18nRoute<'en' | 'fr'>[]. For those, files and ids resolve to string and languages to the declared union.

@dts-stn/react-router-i18n-routes/react

Every hook and component takes the routing instance as its first argument (hooks) or routing prop (components). All of them read the current location from React Router, so they must render inside a router.

useLanguage(routing)

Returns { currentLanguage, alternateLanguage, alternateLanguages } for the current location. alternateLanguage is set only when there is exactly one other language, which is the bilingual case.

useCurrentLanguage(routing)

The language of the current location, or undefined (routing.getLanguage(pathname)).

useI18nRouteMatch(routing)

{ route, language, params } for the current location, or undefined (routing.findRouteByPath(pathname)).

useI18nPath(routing, file, options?)

routing.getPath(), with options.language defaulting to the current language. Throws LANGUAGE_NOT_DETERMINED if neither is available. Use it for navigate(), <Form action>, and anything else that needs a path rather than a link.

useLanguageSwitchPath(routing, language?)

The current page in language (default: the alternate language), keeping its params, search, and hash. Returns undefined outside the i18n routes.

useIsCurrentRoute(routing, file, options?)

Whether the current location is file's page route, in any language. With options.params, the matched route's params must also be equal. Use it for aria-current="page".

useIsNavigatingTo(routing, file, options?)

Whether a navigation to file's page route is loading. options.language defaults to the current language, and options.params also compares the matched route's params. Use it for pending indicators. Requires a data router (framework mode, createBrowserRouter(), or createMemoryRouter()).

For both useIsCurrentRoute() and useIsNavigatingTo():

  • file may be undefined (the result is then false), so you can call them unconditionally from components whose links are not always i18n routes.
  • options.params compares only the matched route's own params, so you can pass the full useParams() object.

A React Router <Link> whose to prop is replaced by routing, file, and the optional language, params, search, and hash. The language defaults to the current language. Every other <Link> prop, including ref, prefetch, and viewTransition, is forwarded.

An unstyled React Router <Link> to useLanguageSwitchPath(), with lang and hrefLang set to the target language. Its to prop is replaced by routing and an optional language. Every other prop, including ref and reloadDocument, is forwarded. It renders nothing outside the i18n routes.

@dts-stn/react-router-i18n-routes/route-config

ExportDescription
toRouteConfigEntries(routes)Converts route definitions into React Router route config entries, recursively.
toPageRouteConfigEntries(pageRoute)Converts one page route into one entry per language, to register it outside the tree.

This entry point depends only on @react-router/dev/routes, so it is safe to import from app/routes.ts.

Behavior details

Path matching

findRouteByPath(), translatePath(), getLanguage(), and the hooks match pathnames with React Router's matchRoutes() across every page route and language. As a result:

  • static segments win over dynamic segments, and dynamic segments win over splats, regardless of declaration order,
  • trailing slashes are ignored,
  • percent-encoded pathnames are decoded, and
  • pathnames are relative to the app's basename.

Path generation

getPath() uses React Router's generatePath(). As a result:

  • a missing required param throws,
  • trailing slashes are removed (/en/ → /en),
  • the result always starts with /, even when a path is declared without one (en/about → /en/about), so links never resolve relative to the current URL, and
  • static segments, search, and hash are returned as declared (/fr/à-propos).

redirect() percent-encodes non-ASCII characters for the Location header (/fr/%C3%A0-propos) and leaves existing percent-escapes unchanged.

Language detection

getLanguage(resource) tries, in order:

  1. the language of the matching page route,
  2. the first path segment, if it is a supported language (/fr/anything → fr), and
  3. only with { acceptLanguage: true } and a Request, the Accept-Language header, using negotiateLanguage().

negotiateLanguage() tries preferences in descending quality order, and ties keep header order. An exact, case-insensitive match wins. Otherwise primary subtags are compared, so fr-CA matches fr and fr matches fr-CA. Entries with q=0, the * wildcard, and malformed entries are ignored.

Cookies, sessions, and user preferences are deliberately left to the application. Read them first, then fall back to getLanguage().

FAQ

Does it translate my content? No. It handles URLs only. Pair it with any translation library (i18next, react-intl, Lingui, …) and pass it the language from useCurrentLanguage() or getLanguage().

Can a page exist in only one language? Not as an i18n route: every page route must declare every language, which guarantees that the language switcher always has a target. Register single-language pages with React Router's own route() in app/routes.ts.

Can I use a /:lang/... style instead of translated paths? Yes. The paths are plain strings, so { en: '/en/about', fr: '/fr/about' } works just as well as translated slugs.

Does it work in declarative or data mode (without app/routes.ts)? The core entry point and the React hooks work with any router. /route-config is specific to framework mode, and useIsNavigatingTo() needs a data router. In other modes, build your route objects from i18nRoutes with getI18nRouteId().

Why do route ids look like DASH-0001-EN? React Router needs a unique id per route, and each page becomes one route per language. Use any id scheme you like; the language suffix is appended in upper case.

Contributing

Issues and pull requests are welcome. See CONTRIBUTING.md for the development setup and release process.

License

MIT

版本与发布记录

@dts-stn/react-router-i18n-routes 共发布 3 个版本,首次发布于 2026/10/19;过去 12 个月发布了 3 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.0.1latest2026/10/1925 天前
1.0.02026/10/1925 天前
0.0.0-stage2026/10/1925 天前

关于 @dts-stn/react-router-i18n-routes 的常见问题

@dts-stn/react-router-i18n-routes 是什么?

Type-safe, translated (i18n) route definitions for React Router (framework mode).

如何安装 @dts-stn/react-router-i18n-routes?

在项目目录运行 npm install @dts-stn/react-router-i18n-routes,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react-router(^7.0.0 || ^8.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@dts-stn/react-router-i18n-routes 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@dts-stn/react-router-i18n-routes 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@dts-stn/react-router-i18n-routes 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@dts-stn/react-router-i18n-routes 可以商用吗?

@dts-stn/react-router-i18n-routes 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@dts-stn/react-router-i18n-routes 的下载量有多少?

根据 npm 官方下载统计,@dts-stn/react-router-i18n-routes 在 2026/10/13 – 2026/11/11 这 30 天内被下载 2,909 次,日均约 97 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@dts-stn/react-router-i18n-routes 还在维护吗?

最新版本 1.0.1 发布于 2026/10/19(25 天前);至今共发布 3 个版本,其中过去 12 个月发布了 3 个;首次发布于 2026/10/19。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。