From 85b75e9b330e22dbf36d06aa6019ddd7652462f9 Mon Sep 17 00:00:00 2001
From: Samuel Stroschein <35429197+samuelstroschein@users.noreply.github.com>
Date: Tue, 9 Dec 2025 15:22:05 -0800
Subject: [PATCH] add paraglide add commands
---
.../react-cra/add-ons/paraglide/README.md | 7 +++
.../add-ons/paraglide/assets/messages/de.json | 9 ++++
.../add-ons/paraglide/assets/messages/en.json | 9 ++++
.../assets/project.inlang/.gitignore | 1 +
.../assets/project.inlang/settings.json | 12 +++++
.../src/components/LocaleSwitcher.tsx.ejs | 44 +++++++++++++++++++
.../assets/src/routes/demo.i18n.tsx.ejs | 34 ++++++++++++++
.../react-cra/add-ons/paraglide/info.json | 25 +++++++++++
.../react-cra/add-ons/paraglide/package.json | 5 +++
.../add-ons/paraglide/small-logo.svg | 4 ++
.../add-ons/start/assets/src/router.tsx.ejs | 16 ++++++-
.../add-ons/start/assets/src/server.ts.ejs | 9 ++++
.../add-ons/start/assets/vite.config.ts.ejs | 9 +++-
.../react-cra/project/base/src/main.tsx.ejs | 11 ++++-
.../project/base/src/routes/__root.tsx.ejs | 25 ++++++++++-
.../react-cra/project/base/tsconfig.json.ejs | 3 +-
.../react-cra/project/base/vite.config.ts.ejs | 9 +++-
frameworks/react-cra/src/checksum.ts | 2 +-
18 files changed, 223 insertions(+), 11 deletions(-)
create mode 100644 frameworks/react-cra/add-ons/paraglide/README.md
create mode 100644 frameworks/react-cra/add-ons/paraglide/assets/messages/de.json
create mode 100644 frameworks/react-cra/add-ons/paraglide/assets/messages/en.json
create mode 100644 frameworks/react-cra/add-ons/paraglide/assets/project.inlang/.gitignore
create mode 100644 frameworks/react-cra/add-ons/paraglide/assets/project.inlang/settings.json
create mode 100644 frameworks/react-cra/add-ons/paraglide/assets/src/components/LocaleSwitcher.tsx.ejs
create mode 100644 frameworks/react-cra/add-ons/paraglide/assets/src/routes/demo.i18n.tsx.ejs
create mode 100644 frameworks/react-cra/add-ons/paraglide/info.json
create mode 100644 frameworks/react-cra/add-ons/paraglide/package.json
create mode 100644 frameworks/react-cra/add-ons/paraglide/small-logo.svg
create mode 100644 frameworks/react-cra/add-ons/start/assets/src/server.ts.ejs
diff --git a/frameworks/react-cra/add-ons/paraglide/README.md b/frameworks/react-cra/add-ons/paraglide/README.md
new file mode 100644
index 00000000..e8baf23f
--- /dev/null
+++ b/frameworks/react-cra/add-ons/paraglide/README.md
@@ -0,0 +1,7 @@
+# Paraglide i18n
+
+This add-on wires up ParaglideJS for localized routing and message formatting.
+
+- Messages live in `project.inlang/messages`.
+- URLs are localized through the Paraglide Vite plugin and router `rewrite` hooks.
+- Run the dev server or build to regenerate the `src/paraglide` outputs.
diff --git a/frameworks/react-cra/add-ons/paraglide/assets/messages/de.json b/frameworks/react-cra/add-ons/paraglide/assets/messages/de.json
new file mode 100644
index 00000000..a44f188a
--- /dev/null
+++ b/frameworks/react-cra/add-ons/paraglide/assets/messages/de.json
@@ -0,0 +1,9 @@
+{
+ "$schema": "https://inlang.com/schema/inlang-message-format",
+ "home_page": "Startseite",
+ "about_page": "Über uns",
+ "example_message": "Willkommen in deiner i18n-App.",
+ "language_label": "Sprache",
+ "current_locale": "Aktuelle Sprache: {locale}",
+ "learn_router": "Paraglide JS lernen"
+}
diff --git a/frameworks/react-cra/add-ons/paraglide/assets/messages/en.json b/frameworks/react-cra/add-ons/paraglide/assets/messages/en.json
new file mode 100644
index 00000000..80f9e247
--- /dev/null
+++ b/frameworks/react-cra/add-ons/paraglide/assets/messages/en.json
@@ -0,0 +1,9 @@
+{
+ "$schema": "https://inlang.com/schema/inlang-message-format",
+ "home_page": "Home page",
+ "about_page": "About page",
+ "example_message": "Welcome to your i18n app.",
+ "language_label": "Language",
+ "current_locale": "Current locale: {locale}",
+ "learn_router": "Learn Paraglide JS"
+}
diff --git a/frameworks/react-cra/add-ons/paraglide/assets/project.inlang/.gitignore b/frameworks/react-cra/add-ons/paraglide/assets/project.inlang/.gitignore
new file mode 100644
index 00000000..06cf6539
--- /dev/null
+++ b/frameworks/react-cra/add-ons/paraglide/assets/project.inlang/.gitignore
@@ -0,0 +1 @@
+cache
diff --git a/frameworks/react-cra/add-ons/paraglide/assets/project.inlang/settings.json b/frameworks/react-cra/add-ons/paraglide/assets/project.inlang/settings.json
new file mode 100644
index 00000000..9bdce4c8
--- /dev/null
+++ b/frameworks/react-cra/add-ons/paraglide/assets/project.inlang/settings.json
@@ -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"
+ }
+}
diff --git a/frameworks/react-cra/add-ons/paraglide/assets/src/components/LocaleSwitcher.tsx.ejs b/frameworks/react-cra/add-ons/paraglide/assets/src/components/LocaleSwitcher.tsx.ejs
new file mode 100644
index 00000000..370d89b8
--- /dev/null
+++ b/frameworks/react-cra/add-ons/paraglide/assets/src/components/LocaleSwitcher.tsx.ejs
@@ -0,0 +1,44 @@
+// Locale switcher refs:
+// - Paraglide docs: https://inlang.com/m/gerre34r/library-inlang-paraglideJs
+// - Router example: https://github.com/TanStack/router/tree/main/examples/react/i18n-paraglide#switching-locale
+import { getLocale, locales, setLocale } from '@/paraglide/runtime'
+import { m } from '@/paraglide/messages'
+
+export default function ParaglideLocaleSwitcher() {
+ const currentLocale = getLocale()
+
+ return (
+
+
{m.current_locale({ locale: currentLocale })}
+
+ {locales.map((locale) => (
+
+ ))}
+
+
+ )
+}
diff --git a/frameworks/react-cra/add-ons/paraglide/assets/src/routes/demo.i18n.tsx.ejs b/frameworks/react-cra/add-ons/paraglide/assets/src/routes/demo.i18n.tsx.ejs
new file mode 100644
index 00000000..6f6200ae
--- /dev/null
+++ b/frameworks/react-cra/add-ons/paraglide/assets/src/routes/demo.i18n.tsx.ejs
@@ -0,0 +1,34 @@
+import { createFileRoute } from "@tanstack/react-router";
+import logo from "../logo.svg";
+import { m } from "@/paraglide/messages";
+import LocaleSwitcher from "../components/LocaleSwitcher";
+
+export const Route = createFileRoute("/demo/i18n")({
+ component: App,
+});
+
+function App() {
+ return (
+
+ );
+}
diff --git a/frameworks/react-cra/add-ons/paraglide/info.json b/frameworks/react-cra/add-ons/paraglide/info.json
new file mode 100644
index 00000000..6bffd29a
--- /dev/null
+++ b/frameworks/react-cra/add-ons/paraglide/info.json
@@ -0,0 +1,25 @@
+{
+ "name": "Paraglide (i18n)",
+ "description": "i18n with localized routing",
+ "phase": "add-on",
+ "modes": ["file-router"],
+ "type": "add-on",
+ "priority": 30,
+ "link": "https://github.com/paraglidejs/paraglide-js",
+ "routes": [
+ {
+ "icon": "Languages",
+ "url": "/demo/i18n",
+ "name": "I18n example",
+ "path": "src/routes/demo.i18n.tsx",
+ "jsName": "I18nDemo"
+ }
+ ],
+ "integrations": [
+ {
+ "type": "header-user",
+ "path": "src/components/LocaleSwitcher.tsx",
+ "jsName": "ParaglideLocaleSwitcher"
+ }
+ ]
+}
diff --git a/frameworks/react-cra/add-ons/paraglide/package.json b/frameworks/react-cra/add-ons/paraglide/package.json
new file mode 100644
index 00000000..77ba5d6d
--- /dev/null
+++ b/frameworks/react-cra/add-ons/paraglide/package.json
@@ -0,0 +1,5 @@
+{
+ "devDependencies": {
+ "@inlang/paraglide-js": "^2.6.0"
+ }
+}
diff --git a/frameworks/react-cra/add-ons/paraglide/small-logo.svg b/frameworks/react-cra/add-ons/paraglide/small-logo.svg
new file mode 100644
index 00000000..abf260ac
--- /dev/null
+++ b/frameworks/react-cra/add-ons/paraglide/small-logo.svg
@@ -0,0 +1,4 @@
+
diff --git a/frameworks/react-cra/add-ons/start/assets/src/router.tsx.ejs b/frameworks/react-cra/add-ons/start/assets/src/router.tsx.ejs
index 2d458778..9aa0235e 100644
--- a/frameworks/react-cra/add-ons/start/assets/src/router.tsx.ejs
+++ b/frameworks/react-cra/add-ons/start/assets/src/router.tsx.ejs
@@ -4,6 +4,8 @@ import * as TanstackQuery from "./integrations/tanstack-query/root-provider";
<% } %>
<% if (addOnEnabled.sentry) { %>
import * as Sentry from "@sentry/tanstackstart-react";
+<% } %><% if (addOnEnabled.paraglide) { %>
+import { deLocalizeUrl, localizeUrl } from "./paraglide/runtime";
<% } %>
// Import the generated route tree
@@ -16,7 +18,12 @@ export const getRouter = () => {
const router = createRouter({
routeTree,
- context: { ...rqContext },
+ context: { ...rqContext },<% if (addOnEnabled.paraglide) { %>
+ // Paraglide URL rewrite docs: https://github.com/TanStack/router/tree/main/examples/react/i18n-paraglide#rewrite-url
+ rewrite: {
+ input: ({ url }) => deLocalizeUrl(url),
+ output: ({ url }) => localizeUrl(url),
+ },<% } %>
defaultPreload: "intent",
Wrap: (props: { children: React.ReactNode }) => {
return (
@@ -30,7 +37,12 @@ export const getRouter = () => {
setupRouterSsrQueryIntegration({router, queryClient: rqContext.queryClient})
<% } else { %>
const router = createRouter({
- routeTree,
+ routeTree,<% if (addOnEnabled.paraglide) { %>
+ // Paraglide URL rewrite docs: https://github.com/TanStack/router/tree/main/examples/react/i18n-paraglide#rewrite-url
+ rewrite: {
+ input: ({ url }) => deLocalizeUrl(url),
+ output: ({ url }) => localizeUrl(url),
+ },<% } %>
scrollRestoration: true,
defaultPreloadStaleTime: 0,
})
diff --git a/frameworks/react-cra/add-ons/start/assets/src/server.ts.ejs b/frameworks/react-cra/add-ons/start/assets/src/server.ts.ejs
new file mode 100644
index 00000000..72ddbae5
--- /dev/null
+++ b/frameworks/react-cra/add-ons/start/assets/src/server.ts.ejs
@@ -0,0 +1,9 @@
+<% if (!addOnEnabled.paraglide) { ignoreFile() } %>import { paraglideMiddleware } from './paraglide/server'
+import handler from '@tanstack/react-start/server-entry'
+
+// Server-side URL localization/redirects for Paraglide
+export default {
+ fetch(req: Request): Promise {
+ return paraglideMiddleware(req, ({ request }) => handler.fetch(request))
+ },
+}
diff --git a/frameworks/react-cra/add-ons/start/assets/vite.config.ts.ejs b/frameworks/react-cra/add-ons/start/assets/vite.config.ts.ejs
index cc31cb71..1d7e84e1 100644
--- a/frameworks/react-cra/add-ons/start/assets/vite.config.ts.ejs
+++ b/frameworks/react-cra/add-ons/start/assets/vite.config.ts.ejs
@@ -1,5 +1,8 @@
import { defineConfig } from 'vite'
import { devtools } from '@tanstack/devtools-vite'
+<% if (addOnEnabled.paraglide) { -%>
+import { paraglideVitePlugin } from "@inlang/paraglide-js"
+<% } -%>
import { tanstackStart } from '@tanstack/react-start/plugin/vite';
import viteReact from '@vitejs/plugin-react'
import viteTsConfigPaths from 'vite-tsconfig-paths'<% if (tailwind) { %>
@@ -8,7 +11,11 @@ import tailwindcss from "@tailwindcss/vite"
<% } %>
const config = defineConfig({
- plugins: [devtools(), <% for(const integration of integrations.filter(i => i.type === 'vite-plugin')) { %><%- integrationImportCode(integration) %>,<% } %>
+ plugins: [devtools(), <% if (addOnEnabled.paraglide) { %>paraglideVitePlugin({
+ project: './project.inlang',
+ outdir: './src/paraglide',
+ strategy: ['url'],
+ }), <% } %><% for(const integration of integrations.filter(i => i.type === 'vite-plugin')) { %><%- integrationImportCode(integration) %>,<% } %>
// this is the plugin that enables path aliases
viteTsConfigPaths({
projects: ['./tsconfig.json'],
diff --git a/frameworks/react-cra/project/base/src/main.tsx.ejs b/frameworks/react-cra/project/base/src/main.tsx.ejs
index 6f09da37..cee4d997 100644
--- a/frameworks/react-cra/project/base/src/main.tsx.ejs
+++ b/frameworks/react-cra/project/base/src/main.tsx.ejs
@@ -105,6 +105,8 @@ import ReactDOM from "react-dom/client";
import { RouterProvider, createRouter } from "@tanstack/react-router";
<% for(const integration of integrations.filter(i => i.type === 'root-provider')) { %>
import * as <%= integration.jsName %> from "<%= relativePath(integration.path) %>";
+<% } %><% if (addOnEnabled.paraglide) { %>
+import { deLocalizeUrl, localizeUrl } from "./paraglide/runtime";
<% } %>
// Import the generated route tree
@@ -122,7 +124,12 @@ const <%= integration.jsName %>Context = <%= integration.jsName %>.getContext();
<% for(const integration of integrations.filter(i => i.type === 'root-provider')) { %>
...<%= integration.jsName %>Context,
<% } %>
- },
+ },<% if (addOnEnabled.paraglide) { %>
+ // Paraglide URL rewrite docs: https://github.com/TanStack/router/tree/main/examples/react/i18n-paraglide#rewrite-url
+ rewrite: {
+ input: ({ url }) => deLocalizeUrl(url),
+ output: ({ url }) => localizeUrl(url),
+ },<% } %>
defaultPreload: "intent",
scrollRestoration: true,
defaultStructuralSharing: true,
@@ -156,4 +163,4 @@ if (rootElement && !rootElement.innerHTML) {
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
-reportWebVitals();<% } %>
\ No newline at end of file
+reportWebVitals();<% } %>
diff --git a/frameworks/react-cra/project/base/src/routes/__root.tsx.ejs b/frameworks/react-cra/project/base/src/routes/__root.tsx.ejs
index 97f5922d..3c453cb8 100644
--- a/frameworks/react-cra/project/base/src/routes/__root.tsx.ejs
+++ b/frameworks/react-cra/project/base/src/routes/__root.tsx.ejs
@@ -1,11 +1,13 @@
<% if (!fileRouter) { ignoreFile() } %>import { <% if (addOnEnabled.start) { %>
- HeadContent<% } else { %>Outlet<% } %><% if (addOnEnabled.start) { %>, Scripts<% } %>, <% if (addOnEnabled["tanstack-query"]) { %>createRootRouteWithContext<% } else { %>createRootRoute<% } %> } from '@tanstack/react-router'
+ HeadContent<% } else { %>Outlet<% } %><% if (addOnEnabled.start) { %>, Scripts<% } %>, <% if (addOnEnabled["tanstack-query"]) { %>createRootRouteWithContext<% } else { %>createRootRoute<% } %><% if (addOnEnabled.paraglide) { %>, redirect<% } %> } from '@tanstack/react-router'
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools';
import { TanStackDevtools } from '@tanstack/react-devtools'
<% if (addOns.length) { %>
import Header from '../components/Header'
<% } %><% for(const integration of integrations.filter(i => i.type === 'layout' || i.type === 'provider' || i.type === 'devtools')) { %>
import <%= integration.jsName %> from '<%= relativePath(integration.path, true) %>'
+<% } %><% if (addOnEnabled.paraglide) { %>
+import { getLocale, shouldRedirect } from '@/paraglide/runtime'
<% } %>
<% if (addOnEnabled.start) { %>
import appCss from '../styles.css?url'
@@ -24,6 +26,25 @@ interface MyRouterContext {
}<% } %>
export const Route = <% if (addOnEnabled["tanstack-query"]) { %>createRootRouteWithContext()<% } else { %>createRootRoute<% } %>({
+<% if (addOnEnabled.paraglide) { %>
+ beforeLoad: async () => {
+ // Other redirect strategies are possible; see
+ // https://github.com/TanStack/router/tree/main/examples/react/i18n-paraglide#offline-redirect
+ if (typeof document !== 'undefined') {
+ document.documentElement.setAttribute('lang', getLocale())
+ }
+
+ <% if (!addOnEnabled.start) { %>// Client-side fallback redirect for SPA/file-router builds. Start apps should
+ // prefer server-side paraglideMiddleware (see start template server.ts).
+ if (typeof window !== 'undefined') {
+ const decision = await shouldRedirect({ url: window.location.href })
+
+ if (decision.redirectUrl) {
+ throw redirect({ href: decision.redirectUrl.href })
+ }
+ }<% } %>
+ },
+<% } %>
<% if (addOnEnabled.start) { %>
head: () => ({
meta: [
@@ -76,7 +97,7 @@ export const Route = <% if (addOnEnabled["tanstack-query"]) { %>createRootRouteW
<% if (addOnEnabled.start) { %>
function RootDocument({ children }: { children: React.ReactNode }) {
return (
-
+ <% if (addOnEnabled.paraglide) { %><% } else { %><% } %>
diff --git a/frameworks/react-cra/project/base/tsconfig.json.ejs b/frameworks/react-cra/project/base/tsconfig.json.ejs
index 6f2c68f4..914216dd 100644
--- a/frameworks/react-cra/project/base/tsconfig.json.ejs
+++ b/frameworks/react-cra/project/base/tsconfig.json.ejs
@@ -10,7 +10,8 @@
/* Bundler mode */
"moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
+ "allowImportingTsExtensions": true,<% if (addOnEnabled.paraglide) { %>
+ "allowJs": true,<% } %>
"verbatimModuleSyntax": <%= addOnEnabled['start'] ? 'false' : 'true' %>,
"noEmit": true,
diff --git a/frameworks/react-cra/project/base/vite.config.ts.ejs b/frameworks/react-cra/project/base/vite.config.ts.ejs
index 734dddb3..38029856 100644
--- a/frameworks/react-cra/project/base/vite.config.ts.ejs
+++ b/frameworks/react-cra/project/base/vite.config.ts.ejs
@@ -1,5 +1,6 @@
<% if (addOnEnabled.start) { ignoreFile() } %>import { defineConfig } from "vite";
-import { devtools } from '@tanstack/devtools-vite'
+import { devtools } from '@tanstack/devtools-vite'<% if (addOnEnabled.paraglide) { %>
+import { paraglideVitePlugin } from "@inlang/paraglide-js"<% } %>
import viteReact from "@vitejs/plugin-react";<% if (tailwind) { %>
import tailwindcss from "@tailwindcss/vite";
<% } %><%if (fileRouter) { %>
@@ -11,7 +12,11 @@ import federationConfig from "./module-federation.config.js";<% } %><% for(const
// https://vitejs.dev/config/
export default defineConfig({
- plugins: [devtools(), <% for(const integration of integrations.filter(i => i.type === 'vite-plugin')) { %><%- integrationImportCode(integration) %>,<% } %> <% if(fileRouter) { %>tanstackRouter({
+ plugins: [devtools(), <% if (addOnEnabled.paraglide) { %>paraglideVitePlugin({
+ project: './project.inlang',
+ outdir: './src/paraglide',
+ strategy: ['url'],
+ }), <% } %><% for(const integration of integrations.filter(i => i.type === 'vite-plugin')) { %><%- integrationImportCode(integration) %>,<% } %> <% if(fileRouter) { %>tanstackRouter({
target: "react",
autoCodeSplitting: true,
}), <% } %>viteReact(<% if (addOnEnabled.compiler) { %>{
diff --git a/frameworks/react-cra/src/checksum.ts b/frameworks/react-cra/src/checksum.ts
index 2c8a71e5..4f7020d3 100644
--- a/frameworks/react-cra/src/checksum.ts
+++ b/frameworks/react-cra/src/checksum.ts
@@ -1,3 +1,3 @@
// This file is auto-generated. Do not edit manually.
// Generated from add-ons, examples, hosts, project, and toolchains directories
-export const contentChecksum = 'b722b3f8235cbf4618c508da1b499a8a6a583d8107eebeb9e2ffe19716ddc7e9'
+export const contentChecksum = '59725486fa40de3b9dbd9b080b1f844dfd17bfdfd5fd79df23f296586ab8ee47'