Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions examples/react/i18n-paraglide/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
count.txt
.env
.nitro
.tanstack
11 changes: 11 additions & 0 deletions examples/react/i18n-paraglide/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"files.watcherExclude": {
"**/routeTree.gen.ts": true
},
"search.exclude": {
"**/routeTree.gen.ts": true
},
"files.readonlyInclude": {
"**/routeTree.gen.ts": true
}
}
141 changes: 141 additions & 0 deletions examples/react/i18n-paraglide/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# TanStack Router example

This example shows how to use Paraglide with TanStack Router. The source code can be found [here](https://github.com/opral/monorepo/tree/main/inlang/packages/paraglide/paraglide-js/examples/tanstack-router).
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use descriptive link text.

The hyperlink label “here” violates MD059 and isn’t accessible; replace it with descriptive text such as “Paraglide TanStack Router example source.” Based on lint hint.

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

3-3: Link text should be descriptive

(MD059, descriptive-link-text)

🤖 Prompt for AI Agents
In examples/react/i18n-paraglide/README.md around line 3, the hyperlink uses
non-descriptive text ("here") which violates MD059; replace the link label with
descriptive text like "Paraglide TanStack Router example source" (or similar) so
the link is accessible and meaningful, updating the Markdown to use that
descriptive phrase as the anchor while keeping the same target URL.


## Getting started

1. Init Paraglide JS

```bash
npx @inlang/paraglide-js@latest init
```

2. Add the vite plugin to your `vite.config.ts`:

```diff
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
+import { paraglideVitePlugin } from "@inlang/paraglide-js";

export default defineConfig({
plugins: [
tanstackRouter({ target: 'react', autoCodeSplitting: true }),
react(),
+ paraglideVitePlugin({
+ project: "./project.inlang",
+ outdir: "./app/paraglide",
+ }),
],
Comment on lines +22 to +29
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Replace hard tabs in the code block.

markdownlint flags the tab-indented lines (MD010). Please switch them to spaces, e.g.:

-	plugins: [
+  plugins: [
     tanstackRouter({ target: 'react', autoCodeSplitting: true }),
     react(),
-		paraglideVitePlugin({
-			project: "./project.inlang",
-			outdir: "./app/paraglide",
-		}),
-	],
+    paraglideVitePlugin({
+      project: "./project.inlang",
+      outdir: "./app/paraglide",
+    }),
+  ],

Based on lint hint.

📝 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
plugins: [
tanstackRouter({ target: 'react', autoCodeSplitting: true }),
react(),
+ paraglideVitePlugin({
+ project: "./project.inlang",
+ outdir: "./app/paraglide",
+ }),
],
plugins: [
tanstackRouter({ target: 'react', autoCodeSplitting: true }),
react(),
paraglideVitePlugin({
project: "./project.inlang",
outdir: "./app/paraglide",
}),
],
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

22-22: Hard tabs
Column: 1

(MD010, no-hard-tabs)


25-25: Hard tabs
Column: 2

(MD010, no-hard-tabs)


26-26: Hard tabs
Column: 2

(MD010, no-hard-tabs)


27-27: Hard tabs
Column: 2

(MD010, no-hard-tabs)


28-28: Hard tabs
Column: 2

(MD010, no-hard-tabs)


29-29: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🤖 Prompt for AI Agents
In examples/react/i18n-paraglide/README.md around lines 22 to 29, the fenced
code block uses hard tabs for indentation which triggers markdownlint MD010;
replace all tab characters inside that code block with spaces (use consistent 2
or 4 spaces per indent level as used elsewhere in the file), ensure alignment is
preserved, and save the file with tabs converted to spaces.

});
```

3. Done :)

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.

## Rewrite URL

If you want to handle how the URL looks when the user changes the locale, you can rewrite the URL in the router.

```diff
import { createRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";
+import { deLocalizeUrl, localizeUrl } from "./paraglide/runtime.js";

const router = createRouter({
routeTree,
+ rewrite: {
+ input: ({ url }) => deLocalizeUrl(url),
+ output: ({ url }) => localizeUrl(url),
},
});
```

In `__root.tsx` add a `beforeLoad` hook to check if the user should be redirected and set the html `lang` attribute.

```ts
import { shouldRedirect } from "../paraglide/runtime";

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 });
}
},
Comment on lines +58 to +69
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Ensure the beforeLoad snippet includes required imports
The snippet references getLocale and redirect but doesn’t import them, so it won’t compile as written. Please add the missing imports for accuracy.

-```ts
-import { shouldRedirect } from "../paraglide/runtime";
+```ts
+import { getLocale, shouldRedirect } from "../paraglide/runtime";
+import { redirect } from "@tanstack/react-router";
🤖 Prompt for AI Agents
In examples/react/i18n-paraglide/README.md around lines 58 to 69, the beforeLoad
snippet uses getLocale and redirect but only imports shouldRedirect, so the
snippet won't compile; update the imports to also import getLocale from
"../paraglide/runtime" and redirect from "@tanstack/react-router" so all
referenced functions are imported at the top of the snippet.

...
});
```

## Typesafe translated pathnames

If you don't want to miss any translated path, you can create a `createTranslatedPathnames` function and pass it to the vite plugin.

```ts
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: Record<PublicRoutePath, Record<Locale, 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',
},
})
```

And import into the Paraglide Vite plguin.

## Server side rendering

For server side rerdering, check out the [TanStack Start guide](https://inlang.com/m/gerre34r/library-inlang-paraglideJs/tanstack-start).
Comment on lines +137 to +141
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix spelling in the prose.

“plguin” (Line 137) and “rerdering” (Line 141) are typos—please change them to “plugin” and “rendering.”

🤖 Prompt for AI Agents
In examples/react/i18n-paraglide/README.md around lines 137 to 141, there are
two typos in the prose: change "plguin" to "plugin" on line 137 and change
"rerdering" to "rendering" on line 141; update the text accordingly so it reads
"Paraglide Vite plugin" and "For server side rendering, check out..." keeping
the rest of the sentence unchanged.

20 changes: 20 additions & 0 deletions examples/react/i18n-paraglide/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-tsrouter-app"
/>
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<title>Create TanStack App - i18n-paraglide</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
7 changes: 7 additions & 0 deletions examples/react/i18n-paraglide/messages/de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "https://inlang.com/schema/inlang-message-format",
"example_message": "Guten Tag {username}",
"hello_about": "Hallo /ueber!",
"home_page": "Startseite",
"about_page": "Über uns"
}
7 changes: 7 additions & 0 deletions examples/react/i18n-paraglide/messages/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$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"
}
29 changes: 29 additions & 0 deletions examples/react/i18n-paraglide/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "tanstack-router-i18n-paraglide",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --port 3000",
"start": "vite --port 3000",
"build": "vite build && tsc",
"serve": "vite preview",
"test": "vitest run"
},
Comment on lines +5 to +11
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Test script requires Vitest

"test": "vitest run" is defined, but vitest isn’t listed here. If it’s not provided at the repo root, add it to devDependencies or adjust the script.


🏁 Script executed:

#!/bin/bash
# Check if vitest is present anywhere in the repo (root or example)
rg -nP '"vitest"\s*:' -g '!**/node_modules/**'

Length of output: 148


🏁 Script executed:

#!/usr/bin/env bash
# Verify presence of vitest in root and example package.json
echo "Checking root package.json for vitest..."
rg -nP '"vitest"\s*:' package.json || echo "No vitest entry in root package.json"
echo
echo "Checking examples/react/i18n-paraglide/package.json for vitest..."
rg -nP '"vitest"\s*:' examples/react/i18n-paraglide/package.json || echo "No vitest entry in example package.json"

Length of output: 173


Add Vitest to examples/react/i18n-paraglide/package.json devDependencies
Vitest is declared in the repo root but missing here—npm run test in this example will fail without it.

🤖 Prompt for AI Agents
In examples/react/i18n-paraglide/package.json around lines 5 to 11, the scripts
reference vitest but the package.json has no devDependency for vitest; add
vitest to devDependencies (preferably the same version used in the repo root) so
npm run test works. Update the file by adding a devDependencies section
including "vitest": "<root-version-or-appropriate-semver>" (or run npm install
--save-dev vitest in that example folder) and commit the change.

"dependencies": {
"@tailwindcss/vite": "^4.1.13",
"@tanstack/react-router": "^1.132.2",
"@tanstack/router-plugin": "^1.132.3",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"tailwindcss": "^4.1.13"
},
Comment on lines +12 to +19
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use workspace: for internal TanStack packages*

Point internal TanStack deps to the workspace to ensure the example consumes local builds.

As per coding guidelines

   "dependencies": {
     "@tailwindcss/vite": "^4.1.13",
-    "@tanstack/react-router": "^1.132.2",
-    "@tanstack/router-plugin": "^1.132.3",
+    "@tanstack/react-router": "workspace:*",
+    "@tanstack/router-plugin": "workspace:*",
     "react": "^19.1.1",
     "react-dom": "^19.1.1",
     "tailwindcss": "^4.1.13"
   },
📝 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
"dependencies": {
"@tailwindcss/vite": "^4.1.13",
"@tanstack/react-router": "^1.132.2",
"@tanstack/router-plugin": "^1.132.3",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"tailwindcss": "^4.1.13"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.13",
"@tanstack/react-router": "workspace:*",
"@tanstack/router-plugin": "workspace:*",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"tailwindcss": "^4.1.13"
},
🤖 Prompt for AI Agents
In examples/react/i18n-paraglide/package.json around lines 12 to 19, the
internal TanStack packages are pinned to external versions; update the
@tanstack/react-router and @tanstack/router-plugin entries to use "workspace:*"
so the example consumes local workspace builds (replace their version strings
with "workspace:*").

"devDependencies": {
"@types/node": "^22.18.6",
"@types/react": "^19.1.13",
"@types/react-dom": "^19.1.9",
"@vitejs/plugin-react": "^5.0.3",
"typescript": "^5.9.2",
"vite": "^7.1.7",
"@inlang/paraglide-js": "^2.4.0"
}
}
1 change: 1 addition & 0 deletions examples/react/i18n-paraglide/project.inlang/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cache
1 change: 1 addition & 0 deletions examples/react/i18n-paraglide/project.inlang/project_id
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
72AJsIR0c0ewzkN33F
12 changes: 12 additions & 0 deletions examples/react/i18n-paraglide/project.inlang/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "https://inlang.com/schema/project-settings",
"baseLocale": "en",
"locales": ["en", "de"],
"modules": [
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js",
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js"
],
"plugin.inlang.messageFormat": {
"pathPattern": "./messages/{locale}.json"
}
}
Binary file added examples/react/i18n-paraglide/public/favicon.ico
Binary file not shown.
Binary file added examples/react/i18n-paraglide/public/logo192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/react/i18n-paraglide/public/logo512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions examples/react/i18n-paraglide/public/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"short_name": "TanStack App",
"name": "Create TanStack App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
3 changes: 3 additions & 0 deletions examples/react/i18n-paraglide/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
12 changes: 12 additions & 0 deletions examples/react/i18n-paraglide/src/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions examples/react/i18n-paraglide/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { StrictMode } from 'react'
import ReactDOM from 'react-dom/client'
import { RouterProvider, createRouter } from '@tanstack/react-router'
import './styles.css'
// Import the generated route tree
import { routeTree } from './routeTree.gen'
import { deLocalizeUrl, localizeUrl } from './paraglide/runtime.js'

// Create a new router instance
const router = createRouter({
routeTree,
context: {},
defaultPreload: 'intent',
scrollRestoration: true,
defaultStructuralSharing: true,
defaultPreloadStaleTime: 0,

rewrite: {
input: ({ url }) => deLocalizeUrl(url),
output: ({ url }) => localizeUrl(url),
},
})

// Register the router instance for type safety
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}

// Render the app
const rootElement = document.getElementById('app')
if (rootElement && !rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)
root.render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
)
}
77 changes: 77 additions & 0 deletions examples/react/i18n-paraglide/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* eslint-disable */

// @ts-nocheck

// noinspection JSUnusedGlobalSymbols

// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.

import { Route as rootRouteImport } from './routes/__root'
import { Route as AboutRouteImport } from './routes/about'
import { Route as IndexRouteImport } from './routes/index'

const AboutRoute = AboutRouteImport.update({
id: '/about',
path: '/about',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRouteImport,
} as any)

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/about': typeof AboutRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/about': typeof AboutRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/about': typeof AboutRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/about'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/about'
id: '__root__' | '/' | '/about'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
AboutRoute: typeof AboutRoute
}

declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/about': {
id: '/about'
path: '/about'
fullPath: '/about'
preLoaderRoute: typeof AboutRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
}
}

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AboutRoute: AboutRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
Loading
Loading