reroute-js 0.45.12-beta.1 → 1.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/README.md +102 -6
  2. package/_/blog/src/client/routes/blog/[layout].tsx +45 -13
  3. package/_/blog/src/client/routes/blog/content/pt-BR/1-hello-world.tsx +27 -0
  4. package/_/blog/src/client/routes/index.tsx +4 -4
  5. package/_/search/src/client/routes/docs/content/content-collections.md +18 -0
  6. package/_/store/package.json +1 -3
  7. package/_/store/src/client/components/Header.tsx +8 -20
  8. package/_/store/src/client/components/ProductCard.tsx +9 -16
  9. package/_/store/src/client/routes/[404].tsx +20 -31
  10. package/_/store/src/client/routes/categories/[category].tsx +23 -28
  11. package/_/store/src/client/routes/categories/index.tsx +19 -27
  12. package/_/store/src/client/routes/index.tsx +27 -37
  13. package/_/store/src/client/routes/products/[id].tsx +35 -48
  14. package/_/store/src/client/routes/products/index.tsx +20 -50
  15. package/_/store/src/client/theme.css +907 -95
  16. package/cli/bin.d.ts +2 -2
  17. package/cli/bin.js +1035 -490
  18. package/cli/bin.js.map +33 -25
  19. package/cli/index.d.ts +2 -2
  20. package/cli/index.js +18 -113
  21. package/cli/index.js.map +5 -5
  22. package/cli/src/cli.d.ts +2 -2
  23. package/cli/src/commands/analyze.d.ts +2 -2
  24. package/cli/src/commands/build.d.ts +2 -2
  25. package/cli/src/commands/dev.d.ts +2 -2
  26. package/cli/src/commands/gen.d.ts +2 -2
  27. package/cli/src/commands/gen.d.ts.map +1 -1
  28. package/cli/src/commands/index.d.ts +2 -2
  29. package/cli/src/commands/init.d.ts +2 -2
  30. package/cli/src/commands/lib/assets.d.ts +2 -2
  31. package/cli/src/commands/lib/bundler.d.ts +2 -2
  32. package/cli/src/commands/lib/command.d.ts +2 -2
  33. package/cli/src/commands/lib/env.d.ts +2 -2
  34. package/cli/src/commands/lib/index.d.ts +3 -3
  35. package/cli/src/commands/lib/index.d.ts.map +1 -1
  36. package/cli/src/commands/lib/log.d.ts +2 -2
  37. package/cli/src/commands/lib/markdown/availability.d.ts +2 -2
  38. package/cli/src/commands/lib/markdown/index.d.ts +2 -2
  39. package/cli/src/commands/lib/markdown/processor.d.ts +2 -2
  40. package/cli/src/commands/lib/production.d.ts +2 -2
  41. package/cli/src/commands/lib/server.d.ts +2 -2
  42. package/cli/src/commands/lib/streaming/analyzer.d.ts +2 -2
  43. package/cli/src/commands/lib/streaming/suspense.d.ts +2 -2
  44. package/cli/src/commands/lib/terminal-ui.d.ts +2 -2
  45. package/cli/src/commands/lib/theme.d.ts +20 -0
  46. package/cli/src/commands/lib/theme.d.ts.map +1 -0
  47. package/cli/src/commands/lib/version.d.ts +2 -2
  48. package/cli/src/commands/og.d.ts +2 -2
  49. package/cli/src/commands/start.d.ts +2 -2
  50. package/cli/src/index.d.ts +2 -2
  51. package/core/index.d.ts +2 -2
  52. package/core/index.js +677 -127
  53. package/core/index.js.map +29 -21
  54. package/core/src/bundler/hash.d.ts +2 -2
  55. package/core/src/bundler/index.d.ts +2 -2
  56. package/core/src/config.d.ts +6 -3
  57. package/core/src/config.d.ts.map +1 -1
  58. package/core/src/content/discovery.d.ts +2 -2
  59. package/core/src/content/index.d.ts +2 -2
  60. package/core/src/content/metadata.d.ts +2 -2
  61. package/core/src/i18n/catalog.d.ts +17 -0
  62. package/core/src/i18n/catalog.d.ts.map +1 -0
  63. package/core/src/i18n/discover.d.ts +19 -0
  64. package/core/src/i18n/discover.d.ts.map +1 -0
  65. package/core/src/i18n/index.d.ts +17 -0
  66. package/core/src/i18n/index.d.ts.map +1 -0
  67. package/core/src/i18n/messages.d.ts +18 -0
  68. package/core/src/i18n/messages.d.ts.map +1 -0
  69. package/core/src/i18n/negotiate.d.ts +18 -0
  70. package/core/src/i18n/negotiate.d.ts.map +1 -0
  71. package/core/src/i18n/pathname.d.ts +34 -0
  72. package/core/src/i18n/pathname.d.ts.map +1 -0
  73. package/core/src/i18n/resolve.d.ts +22 -0
  74. package/core/src/i18n/resolve.d.ts.map +1 -0
  75. package/core/src/i18n/types.d.ts +68 -0
  76. package/core/src/i18n/types.d.ts.map +1 -0
  77. package/core/src/index.d.ts +3 -2
  78. package/core/src/index.d.ts.map +1 -1
  79. package/core/src/llms/extractor.d.ts +2 -2
  80. package/core/src/llms/formatter.d.ts +2 -2
  81. package/core/src/llms/full-generator.d.ts +2 -2
  82. package/core/src/llms/index-generator.d.ts +2 -2
  83. package/core/src/llms/index.d.ts +2 -2
  84. package/core/src/og/discovery.d.ts +2 -2
  85. package/core/src/og/index.d.ts +2 -2
  86. package/core/src/og/meta.d.ts +2 -2
  87. package/core/src/og/render.d.ts +2 -2
  88. package/core/src/og/types.d.ts +2 -2
  89. package/core/src/robots/discovery.d.ts +2 -2
  90. package/core/src/robots/generator.d.ts +2 -2
  91. package/core/src/robots/index.d.ts +2 -2
  92. package/core/src/robots/policies.d.ts +2 -2
  93. package/core/src/rss/discovery.d.ts +4 -2
  94. package/core/src/rss/discovery.d.ts.map +1 -1
  95. package/core/src/rss/generator.d.ts +2 -2
  96. package/core/src/rss/index.d.ts +2 -2
  97. package/core/src/sitemap/discovery.d.ts +7 -2
  98. package/core/src/sitemap/discovery.d.ts.map +1 -1
  99. package/core/src/sitemap/generator.d.ts +2 -2
  100. package/core/src/sitemap/generator.d.ts.map +1 -1
  101. package/core/src/sitemap/index.d.ts +2 -2
  102. package/core/src/ssr/index.d.ts +2 -2
  103. package/core/src/ssr/lib/cache.d.ts +2 -2
  104. package/core/src/ssr/lib/cleanup.d.ts +2 -2
  105. package/core/src/ssr/lib/collections.d.ts +2 -2
  106. package/core/src/ssr/lib/compression.d.ts +2 -2
  107. package/core/src/ssr/lib/compute/content.d.ts +2 -9
  108. package/core/src/ssr/lib/compute/content.d.ts.map +1 -1
  109. package/core/src/ssr/lib/compute/index.d.ts +2 -2
  110. package/core/src/ssr/lib/compute/layouts.d.ts +2 -2
  111. package/core/src/ssr/lib/compute/routes.d.ts +2 -2
  112. package/core/src/ssr/lib/compute/routes.d.ts.map +1 -1
  113. package/core/src/ssr/lib/content-path.d.ts +22 -0
  114. package/core/src/ssr/lib/content-path.d.ts.map +1 -0
  115. package/core/src/ssr/lib/data.d.ts +2 -2
  116. package/core/src/ssr/lib/data.d.ts.map +1 -1
  117. package/core/src/ssr/lib/html.d.ts +2 -2
  118. package/core/src/ssr/lib/imports.d.ts +2 -2
  119. package/core/src/ssr/lib/index.d.ts +2 -2
  120. package/core/src/ssr/lib/layouts.d.ts +2 -2
  121. package/core/src/ssr/lib/metadata.d.ts +2 -2
  122. package/core/src/ssr/lib/metadata.d.ts.map +1 -1
  123. package/core/src/ssr/lib/mime.d.ts +2 -2
  124. package/core/src/ssr/lib/modules.d.ts +4 -4
  125. package/core/src/ssr/lib/modules.d.ts.map +1 -1
  126. package/core/src/ssr/lib/path.d.ts +2 -2
  127. package/core/src/ssr/lib/preload.d.ts +2 -2
  128. package/core/src/ssr/lib/preload.d.ts.map +1 -1
  129. package/core/src/ssr/lib/route-match.d.ts +2 -2
  130. package/core/src/ssr/lib/scripts/collections.d.ts +2 -2
  131. package/core/src/ssr/lib/scripts/data.d.ts +3 -3
  132. package/core/src/ssr/lib/scripts/data.d.ts.map +1 -1
  133. package/core/src/ssr/lib/scripts/escape.d.ts +2 -2
  134. package/core/src/ssr/lib/scripts/feeds.d.ts +2 -2
  135. package/core/src/ssr/lib/scripts/index.d.ts +2 -2
  136. package/core/src/ssr/lib/seed.d.ts +2 -2
  137. package/core/src/ssr/lib/seed.d.ts.map +1 -1
  138. package/core/src/ssr/lib/serialize.d.ts +2 -2
  139. package/core/src/ssr/lib/setup.d.ts +4 -4
  140. package/core/src/ssr/lib/setup.d.ts.map +1 -1
  141. package/core/src/ssr/lib/styles.d.ts +4 -4
  142. package/core/src/ssr/lib/styles.d.ts.map +1 -1
  143. package/core/src/ssr/lib/template.d.ts +2 -2
  144. package/core/src/ssr/lib/types.d.ts +2 -2
  145. package/core/src/ssr/render.d.ts +5 -3
  146. package/core/src/ssr/render.d.ts.map +1 -1
  147. package/core/src/ssr/stream.d.ts +5 -3
  148. package/core/src/ssr/stream.d.ts.map +1 -1
  149. package/elysia/index.d.ts +2 -2
  150. package/elysia/index.js +891 -196
  151. package/elysia/index.js.map +35 -26
  152. package/elysia/src/index.d.ts +2 -2
  153. package/elysia/src/libs/assets.d.ts +2 -2
  154. package/elysia/src/libs/cache.d.ts +2 -2
  155. package/elysia/src/libs/caching.d.ts +2 -2
  156. package/elysia/src/libs/http.d.ts +2 -2
  157. package/elysia/src/libs/i18n.d.ts +26 -0
  158. package/elysia/src/libs/i18n.d.ts.map +1 -0
  159. package/elysia/src/libs/image.d.ts +2 -2
  160. package/elysia/src/libs/index.d.ts +2 -2
  161. package/elysia/src/libs/llms.d.ts +2 -2
  162. package/elysia/src/libs/response.d.ts +2 -2
  163. package/elysia/src/libs/serving.d.ts +2 -2
  164. package/elysia/src/plugin.d.ts +2 -2
  165. package/elysia/src/routes/artifacts.d.ts +2 -2
  166. package/elysia/src/routes/content.d.ts +2 -2
  167. package/elysia/src/routes/image.d.ts +2 -2
  168. package/elysia/src/routes/index.d.ts +2 -2
  169. package/elysia/src/routes/internal.d.ts +2 -2
  170. package/elysia/src/routes/llms.d.ts +2 -2
  171. package/elysia/src/routes/og.d.ts +2 -2
  172. package/elysia/src/routes/redirects.d.ts +2 -2
  173. package/elysia/src/routes/robots.d.ts +2 -2
  174. package/elysia/src/routes/rss.d.ts +2 -2
  175. package/elysia/src/routes/rss.d.ts.map +1 -1
  176. package/elysia/src/routes/search.d.ts +2 -2
  177. package/elysia/src/routes/search.d.ts.map +1 -1
  178. package/elysia/src/routes/sitemap.d.ts +2 -2
  179. package/elysia/src/routes/ssr.d.ts +3 -3
  180. package/elysia/src/routes/ssr.d.ts.map +1 -1
  181. package/elysia/src/routes/static.d.ts +2 -2
  182. package/elysia/src/types.d.ts +2 -2
  183. package/package.json +8 -10
  184. package/react/index.d.ts +2 -2
  185. package/react/index.js +560 -170
  186. package/react/index.js.map +12 -10
  187. package/react/src/components/ClientOnly.d.ts +2 -2
  188. package/react/src/components/ContentRoute.d.ts +3 -3
  189. package/react/src/components/ContentRoute.d.ts.map +1 -1
  190. package/react/src/components/Image.d.ts +2 -2
  191. package/react/src/components/LazyRoute.d.ts +2 -2
  192. package/react/src/components/Link.d.ts +2 -2
  193. package/react/src/components/Link.d.ts.map +1 -1
  194. package/react/src/components/Markdown.d.ts +2 -2
  195. package/react/src/components/Outlet.d.ts +2 -2
  196. package/react/src/components/index.d.ts +2 -2
  197. package/react/src/hooks/index.d.ts +2 -2
  198. package/react/src/hooks/useContent.d.ts +2 -2
  199. package/react/src/hooks/useContent.d.ts.map +1 -1
  200. package/react/src/hooks/useData.d.ts +2 -2
  201. package/react/src/hooks/useFeed.d.ts +2 -2
  202. package/react/src/hooks/useLayoutData.d.ts +2 -2
  203. package/react/src/hooks/useLlms.d.ts +2 -2
  204. package/react/src/hooks/useNavigate.d.ts +2 -2
  205. package/react/src/hooks/useParams.d.ts +2 -2
  206. package/react/src/hooks/useRouter.d.ts +2 -2
  207. package/react/src/hooks/useSearch.d.ts +2 -2
  208. package/react/src/hooks/useSearch.d.ts.map +1 -1
  209. package/react/src/hooks/useSearchParams.d.ts +2 -2
  210. package/react/src/hooks/useToc.d.ts +2 -2
  211. package/react/src/i18n/LocaleProvider.d.ts +49 -0
  212. package/react/src/i18n/LocaleProvider.d.ts.map +1 -0
  213. package/react/src/i18n/helpers.d.ts +24 -0
  214. package/react/src/i18n/helpers.d.ts.map +1 -0
  215. package/react/src/i18n/index.d.ts +13 -0
  216. package/react/src/i18n/index.d.ts.map +1 -0
  217. package/react/src/index.d.ts +3 -2
  218. package/react/src/index.d.ts.map +1 -1
  219. package/react/src/lib/collection.d.ts +2 -2
  220. package/react/src/lib/content.d.ts +4 -3
  221. package/react/src/lib/content.d.ts.map +1 -1
  222. package/react/src/lib/head.d.ts +2 -2
  223. package/react/src/lib/index.d.ts +2 -2
  224. package/react/src/lib/lazy-route.d.ts +2 -2
  225. package/react/src/lib/route-loader.d.ts +2 -2
  226. package/react/src/providers/ContentProvider.d.ts +3 -3
  227. package/react/src/providers/ContentProvider.d.ts.map +1 -1
  228. package/react/src/providers/RerouteProvider.d.ts +8 -6
  229. package/react/src/providers/RerouteProvider.d.ts.map +1 -1
  230. package/react/src/providers/RouterProvider.d.ts +2 -2
  231. package/react/src/providers/index.d.ts +2 -2
  232. package/react/src/types/any.d.ts +2 -2
  233. package/react/src/types/index.d.ts +2 -2
  234. package/react/src/types/router.d.ts +2 -2
  235. package/telemetry/react.d.ts +2 -2
  236. package/telemetry/react.js +3 -3
  237. package/telemetry/react.js.map +1 -1
  238. package/telemetry/server.d.ts +2 -2
  239. package/telemetry/server.js +56 -4
  240. package/telemetry/server.js.map +20 -15
  241. package/telemetry/src/react/api.d.ts +2 -2
  242. package/telemetry/src/react/context.d.ts +2 -2
  243. package/telemetry/src/react/index.d.ts +2 -2
  244. package/telemetry/src/react/telemetry.d.ts +2 -2
  245. package/telemetry/src/server/context.d.ts +2 -2
  246. package/telemetry/src/server/headers/extractor.d.ts +2 -2
  247. package/telemetry/src/server/headers/index.d.ts +2 -2
  248. package/telemetry/src/server/headers/presets.d.ts +2 -2
  249. package/telemetry/src/server/index.d.ts +2 -2
  250. package/telemetry/src/server/instrumentation.d.ts +2 -2
  251. package/telemetry/src/server/plugin.d.ts +2 -2
  252. package/telemetry/src/server/sourcemap.d.ts +2 -2
  253. package/cli/src/commands/lib/tailwind.d.ts +0 -20
  254. package/cli/src/commands/lib/tailwind.d.ts.map +0 -1
package/README.md CHANGED
@@ -22,6 +22,12 @@ Reroute is a feature-rich file-based routing framework for building fullstack Re
22
22
  - [Environment Files](#environment-files)
23
23
  - [Security](#security)
24
24
  - [TypeScript Support](#typescript-support)
25
+ - [🌍 Internationalization (i18n)](#-internationalization-i18n)
26
+ - [Enable](#enable)
27
+ - [URL Rules](#url-rules)
28
+ - [UI Strings](#ui-strings)
29
+ - [SEO & Feeds](#seo--feeds)
30
+ - [Config](#config)
25
31
  - [📝 Markdown Routes](#-markdown-routes)
26
32
  - [Installation](#installation)
27
33
  - [Creating Markdown Routes](#creating-markdown-routes)
@@ -74,6 +80,7 @@ Reroute is a feature-rich file-based routing framework for building fullstack Re
74
80
 
75
81
  ### 📄 Content Management
76
82
  - 📚 **Content Collections** - Organize content by collections (blog posts, docs, etc.)
83
+ - 🌍 **Internationalization** - Opt-in, collection-scoped i18n via `locales/` + `content/{locale}/`
77
84
  - 🔍 **Content Discovery** - Automatic scanning and indexing of content files
78
85
  - 🏷️ **Metadata Extraction** - Support for content metadata
79
86
  - 📑 **Content Registry** - Pre-generated content index for fast lookups
@@ -116,8 +123,11 @@ Reroute is a feature-rich file-based routing framework for building fullstack Re
116
123
  - `useLlms()` - Discover LLM-friendly content URLs for current route
117
124
  - `useSearch()` - Client-side content search with debouncing
118
125
  - `useToc()` - Auto-extract table of contents from markdown headings
126
+ - 🌍 **i18n**:
127
+ - `t(key, values?)` - Plain translator (ICU MessageFormat); not a hook
128
+ - `useLocale()` - Active locale, available locales, and `setLocale()`
119
129
  - 🧩 **Components**:
120
- - `<Link>` - Client-side navigation with automatic prefetching
130
+ - `<Link>` - Client-side navigation with automatic prefetching (localizes content hrefs)
121
131
  - `<Image>` - Optimized images with automatic format negotiation and lazy loading
122
132
  - `<Outlet>` - Render nested route components
123
133
  - `<ContentRoute>` - Dynamic content rendering with metadata injection
@@ -169,11 +179,9 @@ Reroute is a feature-rich file-based routing framework for building fullstack Re
169
179
  - ⚡ **React 19 Streaming** - Native `renderToReadableStream` for optimal performance
170
180
 
171
181
  ### 🎨 Styling & Theming
172
- - 🎨 **Tailwind CSS v4 Support** - Automatic detection and compilation with CSS-first configuration
182
+ - 🎨 **Native CSS** - Author styles in `src/client/theme.css` with no CSS framework required
173
183
  - ⚡ **Live CSS Reload** - Instant style updates in development mode
174
- - 🎯 **Modern CSS Features** - `@theme` and `@utility` directives support
175
- - 🚀 **Oxide Engine** - 5x faster builds with the new Tailwind compiler
176
- - 🔧 **Zero Config** - Works out of the box when `@tailwindcss/cli` is installed
184
+ - 🔧 **Zero Config** - Theme CSS is synced automatically during `reroute gen`
177
185
 
178
186
  ### 🎨 Developer Experience
179
187
  - 📁 **Project Templates** - Quick start with `basic`, `blog`, or `store` templates
@@ -208,6 +216,7 @@ Reroute is a feature-rich file-based routing framework for building fullstack Re
208
216
  ### 🗺️ Sitemap Generation
209
217
  - 🔍 **Auto-Discovery** - Automatically discovers static routes, content collections, and ssr.data routes
210
218
  - 📄 **XML Generation** - Standards-compliant sitemap.xml following Google's specifications
219
+ - 🌍 **i18n Alternates** - `xhtml:link` hreflang entries for localized content
211
220
  - 💾 **Smart Caching** - Generated on first request and cached according to maxAge
212
221
  - 📊 **Auto-Pagination** - Automatically splits into multiple files for sites with 50k+ URLs
213
222
  - 🎨 **Custom Extractors** - Content-agnostic URL and date extraction via configuration
@@ -225,6 +234,7 @@ Reroute is a feature-rich file-based routing framework for building fullstack Re
225
234
  - 🔍 **Auto-Discovery** - Discovers content collections, route ssr.data, and layout ssr.data
226
235
  - 📄 **RSS & Atom Support** - Standards-compliant RSS 2.0 and Atom 1.0 formats
227
236
  - 📚 **Per-Collection Feeds** - Each collection gets its own feed (e.g., /blog/feed.xml)
237
+ - 🌍 **Locale Filter** - `?lang=` filters feed items by content locale
228
238
  - 💾 **Smart Caching** - Generated on first request and cached according to maxAge
229
239
  - 🎨 **Custom Extractors** - Content-agnostic extraction for title, description, author, pubDate
230
240
 
@@ -623,6 +633,92 @@ interface ImportMeta {
623
633
  }
624
634
  ```
625
635
 
636
+ ## 🌍 Internationalization (i18n)
637
+
638
+ Opt-in and **collection-scoped** — not site-wide locale prefixes. Enabled only when a `locales/` directory exists at the app root.
639
+
640
+ ### Enable
641
+
642
+ ```
643
+ locales/
644
+ en-US.json # default locale catalog
645
+ pt-BR.json
646
+ src/client/routes/blog/content/
647
+ hello-world.tsx # default locale (unprefixed URL)
648
+ pt-BR/
649
+ hello-world.tsx # /blog/pt-BR/hello-world
650
+ ```
651
+
652
+ ```json
653
+ // locales/en-US.json
654
+ {
655
+ "nav": { "home": "Home" },
656
+ "blog": {
657
+ "postsCount": "{count, plural, =0 {No posts} one {# post} other {# posts}}"
658
+ }
659
+ }
660
+ ```
661
+
662
+ ### URL Rules
663
+
664
+ | Locale | URL |
665
+ |--------|-----|
666
+ | Default (`en-US`) | `/blog/hello-world` |
667
+ | Other | `/blog/pt-BR/hello-world` |
668
+ | Non-content routes | unchanged (`/about`, `/`) |
669
+
670
+ - Flat files under `content/` are the default locale; nest under `content/{locale}/` for translations
671
+ - Collections without locale folders behave as before
672
+ - Default-locale prefix (`/blog/en-US/...`) 302s to the unprefixed URL
673
+ - Optional negotiate: `Accept-Language` / cookie can 302 default content URLs to a localized path (`i18n.redirectContentOnNegotiate`, default `true`)
674
+
675
+ ### UI Strings
676
+
677
+ ```tsx
678
+ import { t, useLocale, Link } from 'reroute-js/react';
679
+
680
+ function Nav() {
681
+ const { locale, locales, setLocale } = useLocale();
682
+ return (
683
+ <>
684
+ <Link to="/">{t('nav.home')}</Link>
685
+ <select value={locale} onChange={(e) => setLocale(e.target.value)}>
686
+ {locales.map((l) => (
687
+ <option key={l} value={l}>{l}</option>
688
+ ))}
689
+ </select>
690
+ <footer>{t('blog.postsCount', { count: 5 })}</footer>
691
+ </>
692
+ );
693
+ }
694
+ ```
695
+
696
+ - `t(key, values?)` — plain function (not a hook); ICU MessageFormat supported
697
+ - `useLocale()` — `{ locale, locales, defaultLocale, enabled, setLocale }`
698
+ - `Link` auto-localizes content hrefs; cookie (`reroute-locale`) + `Accept-Language` resolve UI locale
699
+
700
+ ### SEO & Feeds
701
+
702
+ - `hreflang` + `og:locale` on content pages
703
+ - Sitemap includes `xhtml:link` alternates
704
+ - RSS / search accept `?lang=` to filter by locale
705
+
706
+ ### Config
707
+
708
+ ```tsx
709
+ // reroute.config.ts
710
+ export default defineConfig({
711
+ i18n: {
712
+ defaultLocale: 'en-US', // unprefixed content URLs
713
+ localesDir: 'locales', // catalog directory
714
+ cookie: 'reroute-locale',
715
+ redirectContentOnNegotiate: true, // 302 default content → localized URL
716
+ },
717
+ });
718
+ ```
719
+
720
+ Feature still requires a `locales/` directory — config only overrides defaults.
721
+
626
722
  ## 📝 Markdown Routes
627
723
 
628
724
  Reroute supports native markdown and MDX files as routes with frontmatter support and syntax highlighting.
@@ -1693,7 +1789,7 @@ Built-in tracing for debugging SSR, 404s, and cache behavior. Automatically trac
1693
1789
 
1694
1790
  - [Examples](./examples) - Example projects to get started
1695
1791
  - [Packages](./packages) - Reroute entrypoints
1696
- - [GitHub](https://github.com/stewones/reroute) - Source code and issues
1792
+ - [GitHub](https://github.com/Intenseloop/reroute) - Source code and issues
1697
1793
 
1698
1794
  ## 🏗️ Build & Deployment
1699
1795
 
@@ -1,5 +1,5 @@
1
- import { Link, Outlet, useData } from 'reroute-js/react';
2
1
  import type { SSR } from 'reroute-js/core';
2
+ import { Link, Outlet, t, useData, useLocale } from 'reroute-js/react';
3
3
 
4
4
  // Layout data type
5
5
  type LayoutData = {
@@ -16,13 +16,17 @@ export const ssr: SSR<LayoutData> = {
16
16
  // Get last updated timestamp from package.json mtime
17
17
  let lastUpdated: string | undefined;
18
18
  try {
19
- const pkgPath = await import('node:path').then(p => p.join(process.cwd(), 'package.json'));
20
- const stats = await import('node:fs/promises').then(fs => fs.stat(pkgPath));
19
+ const pkgPath = await import('node:path').then((p) =>
20
+ p.join(process.cwd(), 'package.json'),
21
+ );
22
+ const stats = await import('node:fs/promises').then((fs) =>
23
+ fs.stat(pkgPath),
24
+ );
21
25
  lastUpdated = stats.mtime.toISOString();
22
26
  } catch {
23
27
  lastUpdated = new Date().toISOString();
24
28
  }
25
-
29
+
26
30
  return {
27
31
  siteTitle: 'Reroute Blog',
28
32
  tagline: 'Fast, Modern, File-Based Routing',
@@ -39,11 +43,11 @@ export const meta = {
39
43
  };
40
44
 
41
45
  export default function BlogLayout() {
42
- // Access layout data - it just works!
43
46
  const data = useData<LayoutData>();
47
+ const { locale, locales, setLocale, enabled } = useLocale();
48
+
44
49
  return (
45
50
  <div style={{ minHeight: '100vh', background: '#f9f9f9' }}>
46
- {/* Blog Header */}
47
51
  <header
48
52
  style={{
49
53
  background: '#fff',
@@ -57,6 +61,8 @@ export default function BlogLayout() {
57
61
  display: 'flex',
58
62
  alignItems: 'center',
59
63
  justifyContent: 'space-between',
64
+ gap: '1rem',
65
+ flexWrap: 'wrap',
60
66
  }}
61
67
  >
62
68
  <Link
@@ -68,7 +74,7 @@ export default function BlogLayout() {
68
74
  color: '#333',
69
75
  }}
70
76
  >
71
- 📝 {data?.siteTitle || 'Blog'}
77
+ 📝 {t('blog.title')}
72
78
  </Link>
73
79
  <div
74
80
  style={{ display: 'flex', gap: '1.5rem', alignItems: 'center' }}
@@ -81,7 +87,7 @@ export default function BlogLayout() {
81
87
  fontSize: '0.875rem',
82
88
  }}
83
89
  >
84
- Home
90
+ {t('nav.home')}
85
91
  </Link>
86
92
  <Link
87
93
  to='/about'
@@ -91,21 +97,43 @@ export default function BlogLayout() {
91
97
  fontSize: '0.875rem',
92
98
  }}
93
99
  >
94
- About
100
+ {t('nav.about')}
95
101
  </Link>
102
+ {enabled && locales.length > 1 && (
103
+ <label
104
+ style={{
105
+ display: 'flex',
106
+ alignItems: 'center',
107
+ gap: '0.35rem',
108
+ fontSize: '0.875rem',
109
+ color: '#666',
110
+ }}
111
+ >
112
+ {t('blog.language')}
113
+ <select
114
+ value={locale}
115
+ onChange={(e) => setLocale(e.target.value)}
116
+ style={{ fontSize: '0.875rem' }}
117
+ >
118
+ {locales.map((loc) => (
119
+ <option key={loc} value={loc}>
120
+ {loc}
121
+ </option>
122
+ ))}
123
+ </select>
124
+ </label>
125
+ )}
96
126
  </div>
97
127
  </nav>
98
128
  </div>
99
129
  </header>
100
130
 
101
- {/* Blog Content */}
102
131
  <main>
103
132
  <div style={{ maxWidth: '800px', margin: '0 auto' }}>
104
133
  <Outlet />
105
134
  </div>
106
135
  </main>
107
136
 
108
- {/* Blog Footer */}
109
137
  <footer
110
138
  style={{
111
139
  marginTop: '4rem',
@@ -117,10 +145,14 @@ export default function BlogLayout() {
117
145
  background: '#fff',
118
146
  }}
119
147
  >
120
- <p>© {new Date().getFullYear()} Reroute Blog. Built with Bun, Elysia, and React.</p>
148
+ <p>
149
+ © {new Date().getFullYear()} {t('blog.title')}. Built with Bun,
150
+ Elysia, and React.
151
+ </p>
121
152
  {data?.totalPosts && (
122
153
  <p style={{ marginTop: '0.5rem', fontSize: '0.75rem' }}>
123
- {data.totalPosts} posts • Last updated: {new Date(data.lastUpdated).toLocaleString()}
154
+ {t('blog.postsCount', { count: data.totalPosts })} • Last updated:{' '}
155
+ {new Date(data.lastUpdated).toLocaleString(locale)}
124
156
  </p>
125
157
  )}
126
158
  </footer>
@@ -0,0 +1,27 @@
1
+ // Page metadata and optional SSR extras. Reroute SSR will use these
2
+ // to generate <title>, <meta name="description"> and append any custom head.
3
+ const meta = {
4
+ title: 'Olá Mundo',
5
+ description: 'Este é o primeiro post usando Reroute 🎉',
6
+ excerpt: 'Este é um ótimo post',
7
+ date: '2025-10-20',
8
+ };
9
+
10
+ const ssr = {
11
+ head: [
12
+ '<meta property="og:type" content="article" />',
13
+ '<meta name="twitter:card" content="summary_large_image" />',
14
+ ],
15
+ };
16
+
17
+ function BlogPost() {
18
+ return (
19
+ <div>
20
+ <h1>Olá Mundo</h1>
21
+ <p>Bem-vindo ao meu blog!!!</p>
22
+ </div>
23
+ );
24
+ }
25
+
26
+ export { meta, ssr };
27
+ export default BlogPost;
@@ -1,10 +1,10 @@
1
- import { Link } from 'reroute-js/react';
1
+ import { Link, t } from 'reroute-js/react';
2
2
 
3
3
  export default function HomePage() {
4
4
  return (
5
5
  <main style={{ padding: '2rem', maxWidth: '1200px', margin: '0 auto' }}>
6
6
  <h1>🏠 Welcome to Reroute!</h1>
7
- <p>This is the home page of your custom file-based routing system.</p>
7
+ <p>{t('home.headline')}</p>
8
8
 
9
9
  <section style={{ marginTop: '2rem' }}>
10
10
  <h2>Navigation</h2>
@@ -25,7 +25,7 @@ export default function HomePage() {
25
25
  borderRadius: '4px',
26
26
  }}
27
27
  >
28
- About
28
+ {t('nav.about')}
29
29
  </Link>
30
30
  <Link
31
31
  to='/blog'
@@ -36,7 +36,7 @@ export default function HomePage() {
36
36
  borderRadius: '4px',
37
37
  }}
38
38
  >
39
- Blog
39
+ {t('home.cta')}
40
40
  </Link>
41
41
  <Link
42
42
  to='/docs'
@@ -35,6 +35,24 @@ src/client/routes/
35
35
  └── post-3.tsx
36
36
  ```
37
37
 
38
+ ### Multilingual content (opt-in)
39
+
40
+ Add a `locales/` folder at the app root with JSON catalogs (e.g. `en-US.json`, `pt-BR.json`) to enable i18n. Then nest translations under `content/{locale}/`:
41
+
42
+ ```
43
+ locales/
44
+ en-US.json
45
+ pt-BR.json
46
+ src/client/routes/blog/content/
47
+ en-US/post-1.md # or keep flat files as the default locale
48
+ pt-BR/post-1.md
49
+ ```
50
+
51
+ - Default locale URLs stay unprefixed: `/blog/post-1`
52
+ - Other locales: `/blog/pt-BR/post-1`
53
+ - Collections without locale folders are unchanged
54
+ - Use `t('nav.home')` for UI strings (ICU MessageFormat supported)
55
+
38
56
  ### Content Files
39
57
 
40
58
  #### Markdown Files
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "{{PROJECT_NAME}}",
3
3
  "version": "0.0.0",
4
- "description": "A Reroute store application with Tailwind CSS v4",
4
+ "description": "A Reroute store application",
5
5
  "private": true,
6
6
  "scripts": {
7
7
  "types": "tsc --noEmit",
@@ -23,8 +23,6 @@
23
23
  "@types/bun": "latest",
24
24
  "@types/react": "^19.2.2",
25
25
  "@types/react-dom": "^19.2.2",
26
- "@tailwindcss/cli": "^4.1.16",
27
- "tailwindcss": "^4.1.16",
28
26
  "typescript": "^5"
29
27
  }
30
28
  }
@@ -2,34 +2,22 @@ import { Link } from 'reroute-js/react';
2
2
 
3
3
  export default function Header() {
4
4
  return (
5
- <header className='sticky top-0 z-50 bg-white border-b border-gray-200 shadow-sm'>
5
+ <header className='site-header'>
6
6
  <div className='container-custom'>
7
- <div className='flex items-center justify-between h-16'>
8
- <Link
9
- to='/'
10
- className='flex items-center gap-2 text-2xl font-bold text-primary-600 hover:text-primary-700 transition-colors'
11
- >
12
- <span className='text-3xl'>🛒</span>
7
+ <div className='header-inner'>
8
+ <Link to='/' className='header-logo'>
9
+ <span className='header-logo-icon'>🛒</span>
13
10
  <span>Reroute Store</span>
14
11
  </Link>
15
12
 
16
- <nav className='flex items-center gap-8'>
17
- <Link
18
- to='/'
19
- className='link-muted font-medium hover:text-primary-600'
20
- >
13
+ <nav className='header-nav'>
14
+ <Link to='/' className='nav-link'>
21
15
  Home
22
16
  </Link>
23
- <Link
24
- to='/products'
25
- className='link-muted font-medium hover:text-primary-600'
26
- >
17
+ <Link to='/products' className='nav-link'>
27
18
  Products
28
19
  </Link>
29
- <Link
30
- to='/categories'
31
- className='link-muted font-medium hover:text-primary-600'
32
- >
20
+ <Link to='/categories' className='nav-link'>
33
21
  Categories
34
22
  </Link>
35
23
  </nav>
@@ -8,12 +8,9 @@ interface ProductCardProps {
8
8
 
9
9
  export default function ProductCard({ product }: ProductCardProps) {
10
10
  return (
11
- <div className='card card-hover group'>
12
- <Link
13
- to={`/products/${product.id}`}
14
- className='block h-full no-underline text-inherit'
15
- >
16
- <div className='w-full h-60 bg-gray-50 flex items-center justify-center p-4'>
11
+ <div className='card card-hover product-card'>
12
+ <Link to={`/products/${product.id}`} className='product-card-link'>
13
+ <div className='product-card-image-wrap'>
17
14
  <Image
18
15
  src={product.image}
19
16
  alt={product.title}
@@ -21,25 +18,21 @@ export default function ProductCard({ product }: ProductCardProps) {
21
18
  height={180}
22
19
  quality={75}
23
20
  sizes='(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 180px'
24
- className='max-w-full max-h-full object-contain relative z-10 bg-white'
21
+ className='product-card-image'
25
22
  style={{ viewTransitionName: `product-image-${product.id}` }}
26
23
  />
27
24
  </div>
28
25
 
29
- <div className='p-4 flex flex-col gap-2 flex-1'>
26
+ <div className='product-card-body'>
30
27
  <div className='badge-primary'>{product.category}</div>
31
28
 
32
- <h3 className='text-base font-semibold text-gray-900 leading-normal line-clamp-2 min-h-[3em] group-hover:text-primary-600 transition-colors'>
33
- {product.title}
34
- </h3>
29
+ <h3 className='product-card-title'>{product.title}</h3>
35
30
 
36
- <div className='flex justify-between items-center mt-auto pt-2'>
37
- <span className='text-xl font-bold text-primary-600'>
38
- ${product.price.toFixed(2)}
39
- </span>
31
+ <div className='product-card-footer'>
32
+ <span className='price'>${product.price.toFixed(2)}</span>
40
33
 
41
34
  {product.rating && (
42
- <div className='flex items-center gap-1 text-sm text-gray-600'>
35
+ <div className='product-rating'>
43
36
  <span>⭐</span>
44
37
  <span>
45
38
  {product.rating.rate} ({product.rating.count})
@@ -6,20 +6,18 @@ export default function NotFoundPage() {
6
6
  <>
7
7
  <Header />
8
8
 
9
- <main className='container-custom py-16'>
10
- <div className='max-w-2xl mx-auto text-center'>
11
- <div className='mb-8 animate-slide-up'>
12
- <div className='text-9xl font-bold text-primary-600 mb-4'>404</div>
13
- <h1 className='text-4xl font-bold text-gray-900 mb-4'>
14
- Page Not Found
15
- </h1>
16
- <p className='text-xl text-gray-600 mb-8'>
9
+ <main className='page-main page-main-lg'>
10
+ <div className='not-found-content'>
11
+ <div className='animate-slide-up'>
12
+ <div className='not-found-code'>404</div>
13
+ <h1 className='not-found-title'>Page Not Found</h1>
14
+ <p className='not-found-lead'>
17
15
  Oops! The page you're looking for doesn't exist. It might have
18
16
  been moved or deleted.
19
17
  </p>
20
18
  </div>
21
19
 
22
- <div className='flex flex-col sm:flex-row gap-4 justify-center mb-12 animate-in'>
20
+ <div className='not-found-actions animate-in'>
23
21
  <Link to='/' className='btn-primary btn-lg'>
24
22
  🏠 Go Home
25
23
  </Link>
@@ -28,32 +26,23 @@ export default function NotFoundPage() {
28
26
  </Link>
29
27
  </div>
30
28
 
31
- <div className='grid grid-cols-1 md:grid-cols-3 gap-6 mt-16 animate-scale'>
32
- <Link
33
- to='/categories'
34
- className='card card-hover p-6 no-underline text-gray-900'
35
- >
36
- <div className='text-4xl mb-3'>🏷️</div>
37
- <h3 className='font-semibold mb-2'>Categories</h3>
38
- <p className='text-sm text-gray-600'>Browse by category</p>
29
+ <div className='not-found-links animate-scale'>
30
+ <Link to='/categories' className='card card-hover card-padded'>
31
+ <div className='not-found-card-icon'>🏷️</div>
32
+ <h3 className='not-found-card-title'>Categories</h3>
33
+ <p className='not-found-card-desc'>Browse by category</p>
39
34
  </Link>
40
35
 
41
- <Link
42
- to='/products'
43
- className='card card-hover p-6 no-underline text-gray-900'
44
- >
45
- <div className='text-4xl mb-3'>📦</div>
46
- <h3 className='font-semibold mb-2'>All Products</h3>
47
- <p className='text-sm text-gray-600'>View our catalog</p>
36
+ <Link to='/products' className='card card-hover card-padded'>
37
+ <div className='not-found-card-icon'>📦</div>
38
+ <h3 className='not-found-card-title'>All Products</h3>
39
+ <p className='not-found-card-desc'>View our catalog</p>
48
40
  </Link>
49
41
 
50
- <Link
51
- to='/'
52
- className='card card-hover p-6 no-underline text-gray-900'
53
- >
54
- <div className='text-4xl mb-3'>⚡</div>
55
- <h3 className='font-semibold mb-2'>Featured</h3>
56
- <p className='text-sm text-gray-600'>See what's popular</p>
42
+ <Link to='/' className='card card-hover card-padded'>
43
+ <div className='not-found-card-icon'>⚡</div>
44
+ <h3 className='not-found-card-title'>Featured</h3>
45
+ <p className='not-found-card-desc'>See what's popular</p>
57
46
  </Link>
58
47
  </div>
59
48
  </div>