Skip to content
Open
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
7 changes: 7 additions & 0 deletions frameworks/react-cra/add-ons/paraglide/README.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cache
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"
}
}
Original file line number Diff line number Diff line change
@@ -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 (
<div
style={{
display: 'flex',
gap: '0.5rem',
alignItems: 'center',
color: 'inherit',
}}
aria-label={m.language_label()}
>
<span style={{ opacity: 0.85 }}>{m.current_locale({ locale: currentLocale })}</span>
<div style={{ display: 'flex', gap: '0.25rem' }}>
{locales.map((locale) => (
<button
key={locale}
onClick={() => setLocale(locale)}
aria-pressed={locale === currentLocale}
style={{
cursor: 'pointer',
padding: '0.35rem 0.75rem',
borderRadius: '999px',
border: '1px solid #d1d5db',
background: locale === currentLocale ? '#0f172a' : 'transparent',
color: locale === currentLocale ? '#f8fafc' : 'inherit',
fontWeight: locale === currentLocale ? 700 : 500,
letterSpacing: '0.01em',
}}
>
{locale.toUpperCase()}
</button>
))}
</div>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -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 (
<div className="text-center">
<header className="min-h-screen flex flex-col items-center justify-center bg-[#282c34] text-white text-[calc(10px+2vmin)] gap-4">
<img
src={logo}
className="h-[40vmin] pointer-events-none animate-[spin_20s_linear_infinite]"
alt="logo"
/>
<p>{m.example_message({ username: "TanStack Router" })}</p>
<a
className="text-[#61dafb] hover:underline"
href="https://inlang.com/m/gerre34r/library-inlang-paraglideJs"
target="_blank"
rel="noopener noreferrer"
>
{m.learn_router()}
</a>
<div className="mt-3">
<LocaleSwitcher />
</div>
</header>
</div>
);
}
25 changes: 25 additions & 0 deletions frameworks/react-cra/add-ons/paraglide/info.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
5 changes: 5 additions & 0 deletions frameworks/react-cra/add-ons/paraglide/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"devDependencies": {
"@inlang/paraglide-js": "^2.6.0"
}
}
4 changes: 4 additions & 0 deletions frameworks/react-cra/add-ons/paraglide/small-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 14 additions & 2 deletions frameworks/react-cra/add-ons/start/assets/src/router.tsx.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 (
Expand All @@ -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,
})
Expand Down
9 changes: 9 additions & 0 deletions frameworks/react-cra/add-ons/start/assets/src/server.ts.ejs
Original file line number Diff line number Diff line change
@@ -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<Response> {
return paraglideMiddleware(req, ({ request }) => handler.fetch(request))
},
}
9 changes: 8 additions & 1 deletion frameworks/react-cra/add-ons/start/assets/vite.config.ts.ejs
Original file line number Diff line number Diff line change
@@ -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) { %>
Expand All @@ -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'],
Expand Down
11 changes: 9 additions & 2 deletions frameworks/react-cra/project/base/src/main.tsx.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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();<% } %>
reportWebVitals();<% } %>
25 changes: 23 additions & 2 deletions frameworks/react-cra/project/base/src/routes/__root.tsx.ejs
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -24,6 +26,25 @@ interface MyRouterContext {
}<% } %>

export const Route = <% if (addOnEnabled["tanstack-query"]) { %>createRootRouteWithContext<MyRouterContext>()<% } 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: [
Expand Down Expand Up @@ -76,7 +97,7 @@ export const Route = <% if (addOnEnabled["tanstack-query"]) { %>createRootRouteW
<% if (addOnEnabled.start) { %>
function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<% if (addOnEnabled.paraglide) { %><html lang={getLocale()}><% } else { %><html lang="en"><% } %>
<head>
<HeadContent />
</head>
Expand Down
3 changes: 2 additions & 1 deletion frameworks/react-cra/project/base/tsconfig.json.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"allowImportingTsExtensions": true,<% if (addOnEnabled.paraglide) { %>
"allowJs": true,<% } %>
Comment on lines +13 to +14
Copy link
Author

Choose a reason for hiding this comment

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

Allow JS is needed to make TS treat JSDoc as types. (Paraglide JS compiles to JS with JSDoc)

"verbatimModuleSyntax": <%= addOnEnabled['start'] ? 'false' : 'true' %>,
"noEmit": true,

Expand Down
9 changes: 7 additions & 2 deletions frameworks/react-cra/project/base/vite.config.ts.ejs
Original file line number Diff line number Diff line change
@@ -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) { %>
Expand All @@ -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) { %>{
Expand Down
2 changes: 1 addition & 1 deletion frameworks/react-cra/src/checksum.ts
Original file line number Diff line number Diff line change
@@ -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'