|
| 1 | +# TanStack Start example |
| 2 | + |
| 3 | +This example shows how to use Paraglide with TanStack Start. The source code can be found [here](https://github.com/opral/monorepo/tree/main/inlang/packages/paraglide/paraglide-js/examples/tanstack-start). |
| 4 | + |
| 5 | +## Getting started |
| 6 | + |
| 7 | +1. Init Paraglide JS |
| 8 | + |
| 9 | +```bash |
| 10 | +npx @inlang/paraglide-js@latest init |
| 11 | +``` |
| 12 | + |
| 13 | +2. Add the vite plugin to your `vite.config.ts`: |
| 14 | + |
| 15 | +```diff |
| 16 | +import { defineConfig } from 'vite' |
| 17 | +import { tanstackStart } from "@tanstack/solid-start/plugin/vite"; |
| 18 | +import react from '@vitejs/plugin-react' |
| 19 | ++import { paraglideVitePlugin } from "@inlang/paraglide-js"; |
| 20 | + |
| 21 | +export default defineConfig({ |
| 22 | + plugins: [ |
| 23 | + tanstackStart(), |
| 24 | + react(), |
| 25 | ++ paraglideVitePlugin({ |
| 26 | ++ project: "./project.inlang", |
| 27 | ++ outdir: "./app/paraglide", |
| 28 | ++ outputStructure: "message-modules", |
| 29 | ++ cookieName: "PARAGLIDE_LOCALE", |
| 30 | ++ strategy: ["url", "cookie", "preferredLanguage", "baseLocale"], |
| 31 | ++ urlPatterns: [ |
| 32 | ++ { |
| 33 | ++ pattern: "/:path(.*)?", |
| 34 | ++ localized: [ |
| 35 | ++ ["en", "/en/:path(.*)?"], |
| 36 | ++ ], |
| 37 | ++ }, |
| 38 | ++ ], |
| 39 | ++ }), |
| 40 | + ], |
| 41 | +}); |
| 42 | +``` |
| 43 | + |
| 44 | +3. Done :) |
| 45 | + |
| 46 | +Run the app and start translating. See the [basics documentation](https://inlang.com/m/gerre34r/library-inlang-paraglideJs/basics) for information on how to use Paraglide's messages, parameters, and locale management. |
| 47 | + |
| 48 | +## Rewrite URL |
| 49 | + |
| 50 | +If you want to handle how the URL looks when the user changes the locale, you can rewrite the URL in the router. |
| 51 | + |
| 52 | +```diff |
| 53 | +import { createRouter } from "@tanstack/solid-router"; |
| 54 | +import { routeTree } from "./routeTree.gen"; |
| 55 | ++import { deLocalizeUrl, localizeUrl } from "./paraglide/runtime.js"; |
| 56 | + |
| 57 | +const router = createRouter({ |
| 58 | + routeTree, |
| 59 | ++ rewrite: { |
| 60 | ++ input: ({ url }) => deLocalizeUrl(url), |
| 61 | ++ output: ({ url }) => localizeUrl(url), |
| 62 | + }, |
| 63 | +}); |
| 64 | +``` |
| 65 | + |
| 66 | +In `server.ts` intercept the request with the paraglideMiddleware. |
| 67 | + |
| 68 | +```ts |
| 69 | +import { paraglideMiddleware } from './paraglide/server.js' |
| 70 | +import handler from '@tanstack/solid-start/server-entry' |
| 71 | + |
| 72 | +export default { |
| 73 | + fetch(req: Request): Promise<Response> { |
| 74 | + return paraglideMiddleware(req, ({ request }) => handler.fetch(request)) |
| 75 | + }, |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +In `__root.tsx` add change the html lang attribute to the current locale. |
| 80 | + |
| 81 | +```tsx |
| 82 | +import { getLocale } from '../paraglide/runtime.js' |
| 83 | + |
| 84 | +function RootDocument({ children }: { children: React.ReactNode }) { |
| 85 | + return ( |
| 86 | + <html lang={getLocale()}> |
| 87 | + <head> |
| 88 | + <HeadContent /> |
| 89 | + </head> |
| 90 | + <body> |
| 91 | + {children} |
| 92 | + <Scripts /> |
| 93 | + </body> |
| 94 | + </html> |
| 95 | + ) |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +## Offline redirect |
| 100 | + |
| 101 | +If you have an application that needs to work offline, you will need to handle the redirect in the client like this. |
| 102 | + |
| 103 | +```ts |
| 104 | +import { shouldRedirect } from "../paraglide/runtime"; |
| 105 | + |
| 106 | +export const Route = createRootRoute({ |
| 107 | + beforeLoad: async () => { |
| 108 | + const decision = await shouldRedirect({ url: window.location.href }); |
| 109 | + |
| 110 | + if (decision.redirectUrl) { |
| 111 | + throw redirect({ href: decision.redirectUrl.href }); |
| 112 | + } |
| 113 | + }, |
| 114 | + ... |
| 115 | +}); |
| 116 | +``` |
| 117 | + |
| 118 | +## Typesafe translated pathnames |
| 119 | + |
| 120 | +If you don't want to miss any translated path, you can create a `createTranslatedPathnames` function and pass it to the vite plugin. |
| 121 | + |
| 122 | +```ts |
| 123 | +import { Locale } from '@/paraglide/runtime' |
| 124 | +import { FileRoutesByTo } from '../routeTree.gen' |
| 125 | + |
| 126 | +type RoutePath = keyof FileRoutesByTo |
| 127 | + |
| 128 | +const excludedPaths = ['admin', 'docs', 'api'] as const |
| 129 | + |
| 130 | +type PublicRoutePath = Exclude< |
| 131 | + RoutePath, |
| 132 | + `${string}${(typeof excludedPaths)[number]}${string}` |
| 133 | +> |
| 134 | + |
| 135 | +type TranslatedPathname = { |
| 136 | + pattern: string |
| 137 | + localized: Array<[Locale, string]> |
| 138 | +} |
| 139 | + |
| 140 | +function toUrlPattern(path: string) { |
| 141 | + return ( |
| 142 | + path |
| 143 | + // catch-all |
| 144 | + .replace(/\/\$$/, '/:path(.*)?') |
| 145 | + // optional parameters: {-$param} |
| 146 | + .replace(/\{-\$([a-zA-Z0-9_]+)\}/g, ':$1?') |
| 147 | + // named parameters: $param |
| 148 | + .replace(/\$([a-zA-Z0-9_]+)/g, ':$1') |
| 149 | + // remove trailing slash |
| 150 | + .replace(/\/+$/, '') |
| 151 | + ) |
| 152 | +} |
| 153 | + |
| 154 | +function createTranslatedPathnames( |
| 155 | + input: Record<PublicRoutePath, Record<Locale, string>>, |
| 156 | +): TranslatedPathname[] { |
| 157 | + return Object.entries(input).map(([pattern, locales]) => ({ |
| 158 | + pattern: toUrlPattern(pattern), |
| 159 | + localized: Object.entries(locales).map( |
| 160 | + ([locale, path]) => |
| 161 | + [locale as Locale, `/${locale}${toUrlPattern(path)}`] satisfies [ |
| 162 | + Locale, |
| 163 | + string, |
| 164 | + ], |
| 165 | + ), |
| 166 | + })) |
| 167 | +} |
| 168 | + |
| 169 | +export const translatedPathnames = createTranslatedPathnames({ |
| 170 | + '/': { |
| 171 | + en: '/', |
| 172 | + de: '/', |
| 173 | + }, |
| 174 | + '/about': { |
| 175 | + en: '/about', |
| 176 | + de: '/ueber', |
| 177 | + }, |
| 178 | +}) |
| 179 | +``` |
| 180 | + |
| 181 | +And import into the Paraglide Vite plguin. |
| 182 | + |
| 183 | +# Prerender routes |
| 184 | + |
| 185 | +You can use use the `localizeHref` function to map the routes to localized versions and import into the pages option in the TanStack Start plugin. For this to work you will need to compile paraglide before the build with the CLI. |
| 186 | + |
| 187 | +```ts |
| 188 | +import { localizeHref } from './paraglide/runtime' |
| 189 | + |
| 190 | +export const prerenderRoutes = ['/', '/about'].map((path) => ({ |
| 191 | + path: localizeHref(path), |
| 192 | + prerender: { |
| 193 | + enabled: true, |
| 194 | + }, |
| 195 | +})) |
| 196 | +``` |
0 commit comments