:从 URL 重写到类型安全的路由路径翻译)
在 TanStack Solid Router 中使用 Paraglide 实现国际化i18n从 URL 重写到类型安全的路由路径翻译【免费下载链接】router A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router导读本文基于仓库中 examples/solid/i18n-paraglide 完整示例讲解如何在 TanStack Solid RouterSolid 版本应用中集成 Paraglide JS 国际化方案覆盖 Vite 插件接入、消息文件组织、URL 本地化重写、beforeLoad语言重定向、以及基于路由表泛型推导的类型安全路径翻译Typesafe translated pathnames四个核心环节。读完本文你将能够独立搭建一个支持多语言如英文/德文、带语言前缀 URL、且路由路径翻译零遗漏的全类型安全 SPA 国际化应用。示例项目概览该示例是一个基于 Vite Solid 的最小 TanStack Router 应用目录结构如下examples/solid/i18n-paraglide/ ├── messages/ │ ├── de.json # 德文消息 │ └── en.json # 英文消息baseLocale ├── project.inlang/ │ ├── project_id │ └── settings.json # inlang 项目配置语言、模块 ├── src/ │ ├── routes/ │ │ ├── __root.tsx # 根路由语言切换、lang 属性、重定向 │ │ ├── about.tsx # /about 页面 │ │ └── index.tsx # 首页 │ ├── main.tsx # 路由实例URL 重写rewrite │ └── routeTree.gen.ts # 由 tanstack/router-plugin 生成的路由树 ├── vite.config.ts # paraglideVitePlugin tanstackRouter 插件配置 └── package.json其中src/paraglide/目录是构建时由 Paraglide 自动生成的消息与运行时模块本示例通过outdir: ./src/paraglide指定不属于源码仓库的静态文件。第一步初始化 Paraglide JS在项目根目录执行官方初始化命令npx inlang/paraglide-jslatest init初始化会在项目中创建project.inlang/目录。查看本示例的 project.inlang/settings.json 可以看到关键配置{ $schema: https://inlang.com/schema/project-settings, baseLocale: en, locales: [en, de], modules: [ https://cdn.jsdelivr.net/npm/inlang/plugin-message-format4/dist/index.js, https://cdn.jsdelivr.net/npm/inlang/plugin-m-function-matcher2/dist/index.js ], plugin.inlang.messageFormat: { pathPattern: ./messages/{locale}.json } }要点说明baseLocale为en声明了基准语言locales列出全部支持语言[en, de]plugin.inlang.messageFormat.pathPattern定义了消息文件按messages/{locale}.json存放因此本示例中消息分别放在 messages/en.json 与 messages/de.json。以英文消息文件为例它使用了 inlang 消息格式并支持{username}这样的参数占位符{ $schema: https://inlang.com/schema/inlang-message-format, example_message: Hello world {username}, hello_about: Hello /about!, home_page: Home page, about_page: About page }对应的德文版本将文案翻译为Guten Tag {username}、Hallo /ueber!、Startseite、Über uns注意德文路径/ueber与英文/about对应这正是后文 URL 翻译要处理的对象。第二步在 vite.config.ts 中挂载 Paraglide 插件在 vite.config.ts 中同时启用 Paraglide 与 TanStack Router 两个 Vite 插件。相比 README 中的最小示例本仓库的实际配置更完整展示了项目化落地时的全部常用参数import { resolve } from node:path import { defineConfig } from vite import viteSolid from vite-plugin-solid import { tanstackRouter } from tanstack/router-plugin/vite import { paraglideVitePlugin } from inlang/paraglide-js import tailwindcss from tailwindcss/vite export default defineConfig({ plugins: [ tailwindcss(), paraglideVitePlugin({ project: ./project.inlang, outdir: ./src/paraglide, outputStructure: message-modules, cookieName: PARAGLIDE_LOCALE, strategy: [url, cookie, preferredLanguage, baseLocale], urlPatterns: [ { pattern: /, localized: [ [en, /], [de, /de], ], }, { pattern: /about, localized: [ [en, /about], [de, /de/ueber], ], }, { pattern: /:path(.*)?, localized: [ [en, /:path(.*)?], [de, /de/:path(.*)?], ], }, ], }), tanstackRouter({ target: solid, autoCodeSplitting: true }), viteSolid(), ], resolve: { alias: { : resolve(__dirname, ./src), }, }, })各配置项含义如下project指向初始化生成的project.inlang目录outdir生成消息与运行时模块的输出目录这里为./src/paraglide因此源码中以/paraglide/messages、/paraglide/runtime导入outputStructure: message-modules按消息模块方式输出便于按需加载与 tree-shakingcookieName指定持久化语言选择的 Cookie 名PARAGLIDE_LOCALEstrategy语言解析策略优先级数组依次为 URL、Cookie、浏览器首选语言、基准语言兜底即URL 优先、Cookie 记忆次之的常见体验设计urlPatterns路由路径与各语言 URL 的映射表。其中/:path(.*)?这一条是关键的兜底规则保证未显式配置的路径在德文下统一带/de前缀同时(.*)?支持可选参数。注意tanstackRouter({ target: solid, autoCodeSplitting: true })的target必须声明为solid而非 React 示例中的react它负责基于src/routes文件系统生成类型安全的路由树routeTree.gen.ts。两个插件协同Paraglide 负责生成src/paraglide运行时Router 插件负责生成路由树。第三步在组件中使用类型安全的消息函数Paraglide 会基于消息文件自动生成带类型的消息函数m。以首页 src/routes/index.tsx 为例import { createFileRoute } from tanstack/solid-router import { m } from /paraglide/messages export const Route createFileRoute(/)({ component: App, }) function App() { return ( div p {m.example_message({ username: TanStack Router!, })} /p /div ) }m.example_message()的调用签名由消息定义中的{username}占位符推导而来缺失参数或拼错消息名都会在编译期报错。/about页面about.tsx同样直接调用m.hello_about()渲染翻译后的文案。第四步通过 router.rewrite 实现 URL 本地化当用户切换语言时URL 中的语言前缀需要随之变化。TanStack Router 的rewrite选项可在此拦截 URLinput负责把带语言前缀的 URL 还原为内部无前缀路径output负责把内部路径写回带语言前缀的外部 URL。在 src/main.tsx 中实现如下import { render } from solid-js/web import { RouterProvider, createRouter } from tanstack/solid-router import ./styles.css import { routeTree } from ./routeTree.gen import { deLocalizeUrl, localizeUrl } from ./paraglide/runtime.js const router createRouter({ routeTree, context: {}, defaultPreload: intent, scrollRestoration: true, defaultStructuralSharing: true, defaultPreloadStaleTime: 0, rewrite: { input: ({ url }) deLocalizeUrl(url), output: ({ url }) localizeUrl(url), }, }) declare module tanstack/solid-router { interface Register { router: typeof router } } const rootElement document.getElementById(app)! if (!rootElement.innerHTML) { render(() RouterProvider router{router} /, rootElement) }deLocalizeUrl(url)将/de/ueber还原为/about供路由内部匹配localizeUrl(url)将/about写作/de/ueber呈现给用户与浏览器。rewrite的实现在packages/solid-router/src/index.tsx中定义属于 Router 核心 API所有框架绑定React/Solid/Vue共用同一套语义。这样应用代码始终面对无前缀的内部路径语言感知的 URL 完全交给重写层处理。第五步在根路由 beforeLoad 中做语言重定向为了让用户直接访问/de/ueber等带前缀 URL 时能正确进入对应语言、并保持html lang属性一致在根路由 src/routes/__root.tsx 的beforeLoad钩子中处理import { Link, Outlet, createRootRoute, redirect } from tanstack/solid-router import { getLocale, locales, setLocale, shouldRedirect, } from /paraglide/runtime import { m } from /paraglide/messages export const Route createRootRoute({ beforeLoad: async () { document.documentElement.setAttribute(lang, getLocale()) const decision await shouldRedirect({ url: window.location.href }) if (decision.redirectUrl) { throw redirect({ href: decision.redirectUrl.href }) } }, component: () ( {/* 导航与语言切换 UI */} div classp-2 flex gap-2 text-lg justify-between div classflex gap-2 text-lg Link to/ activeProps{{ class: font-bold }} activeOptions{{ exact: true }} {m.home_page()} /Link Link to/about activeProps{{ class: font-bold }} {m.about_page()} /Link /div div classflex gap-2 text-lg {locales.map((locale) ( button onClick{() setLocale(locale)} >import { Locale } from /paraglide/runtime import { FileRoutesByTo } from ../routeTree.gen type RoutePath keyof FileRoutesByTo const excludedPaths [admin, docs, api] as const type PublicRoutePath Exclude RoutePath, ${string}${(typeof excludedPaths)[number]}${string} type TranslatedPathname { pattern: string localized: Array[Locale, string] } function toUrlPattern(path: string) { return ( path // catch-all .replace(/\/\$$/, /:path(.*)?) // optional parameters: {-$param} .replace(/\{-\$([a-zA-Z0-9_])\}/g, :$1?) // named parameters: $param .replace(/\$([a-zA-Z0-9_])/g, :$1) // remove trailing slash .replace(/\/$/, ) ) } function createTranslatedPathnames( input: RecordPublicRoutePath, RecordLocale, string, ): TranslatedPathname[] { return Object.entries(input).map(([pattern, locales]) ({ pattern: toUrlPattern(pattern), localized: Object.entries(locales).map( ([locale, path]) [locale as Locale, /${locale}${toUrlPattern(path)}] satisfies [ Locale, string, ], ), })) } export const translatedPathnames createTranslatedPathnames({ /: { en: /, de: /, }, /about: { en: /about, de: /ueber, }, })这段代码的核心思想keyof FileRoutesByTo拿到 Router 插件生成的全部内部路径联合类型通过Exclude将admin、docs、api等不需要翻译的内部路径剔除得到PublicRoutePathtoUrlPattern把 TanStack 的路径语法$param命名参数、{-$param}可选参数、/$catch-all转换为 Paraglide 的 URL pattern 语法createTranslatedPathnames强制要求每一条公开路径 × 每一个语言都必须提供翻译缺一条即类型报错最终导出的translatedPathnames传入 Paraglide Vite 插件插件据此自动生成各语言路径的重写规则。该方案与 README 中urlPatterns手写映射互补小项目手写urlPatterns足够路由增多后可用createTranslatedPathnames保证翻译零遗漏。运行与验证在示例目录下执行pnpm install pnpm dev # 等价于 vite --port 3000打开http://localhost:3000可看到英文界面Hello world TanStack Router!点击de按钮后 URL 变为/de首页文案切换为Guten Tag TanStack Router!导航至/de/ueber时显示Hallo /ueber!同时html lang属性随语言同步更新。直接刷新/de/ueber会命中beforeLoad中的shouldRedirect与rewrite.input保证深链直达的 URL 也能被正确本地化解读。关于 SSR 的说明README 指出若需要服务端渲染SSR场景下的国际化应参考examples/react/start-i18n-paraglide对应的 TanStack Start 方案仓库内对应示例为 examples/react/start-i18n-paraglide。本文所有 URL 重写与语言切换逻辑均为客户端 SPA 实现适用于纯客户端渲染应用SSR 场景还需考虑服务端读取 Cookie、注入初始语言与避免水合不一致等问题。小结通过本示例可以看到Paraglide 与 TanStack Solid Router 的分工非常清晰Paraglide 负责消息编译、语言解析与 URL 翻译规则的生成Router 则通过rewrite与beforeLoad两个机制承接 URL 本地化与语言重定向。两者结合routeTree.gen.ts的类型推导能力让翻译遗漏这类国际化常见问题在编译期就被拦截最终交付一个路径、文案、lang属性完全一致且类型安全的国际化应用。【免费下载链接】router A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考