rothzerg 0.1.4 → 0.1.5

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 (266) hide show
  1. package/package.json +1 -1
  2. package/templates/{website-mobile/overrides → website-blog}/App.tsx +0 -5
  3. package/templates/{_base-website → website-blog}/client.tsx +0 -2
  4. package/templates/website-blog/{overrides/config.ts → config.ts} +3 -13
  5. package/templates/website-blog/data/blog/authors.json +18 -0
  6. package/templates/website-blog/data/blog/categories.json +19 -0
  7. package/templates/website-blog/data/blog/posts.json +22 -0
  8. package/templates/website-blog/data/blog/tags.json +16 -0
  9. package/templates/website-blog/i18n/config.ts +43 -0
  10. package/templates/website-blog/i18n/sections/common/_index.ts +5 -0
  11. package/templates/website-blog/i18n/sections/common/en.ts +24 -0
  12. package/templates/website-blog/i18n/sections/home/_index.ts +5 -0
  13. package/templates/website-blog/i18n/sections/home/en.ts +7 -0
  14. package/templates/website-blog/i18n/translations.ts +18 -0
  15. package/templates/{_base-website → website-blog}/index.ts +39 -67
  16. package/templates/website-blog/pages/404.tsx +31 -0
  17. package/templates/website-blog/pages/blog/author.tsx +53 -0
  18. package/templates/website-blog/pages/blog/category.tsx +52 -0
  19. package/templates/website-blog/pages/blog/index.tsx +59 -0
  20. package/templates/website-blog/pages/blog/post.tsx +87 -0
  21. package/templates/website-blog/pages/blog/tag.tsx +49 -0
  22. package/templates/website-blog/pages/index.tsx +78 -0
  23. package/templates/website-blog/pages/privacy.tsx +45 -0
  24. package/templates/website-blog/pages/support.tsx +48 -0
  25. package/templates/website-blog/pages/terms.tsx +45 -0
  26. package/templates/website-blog/rothzerg.template.json +52 -5
  27. package/templates/website-blog/styles/404.css +1 -0
  28. package/templates/website-blog/styles/_app.css +116 -0
  29. package/templates/website-blog/styles/_shared.css +1 -0
  30. package/templates/website-blog/styles/index.css +1 -0
  31. package/templates/website-blog/styles/privacy.css +1 -0
  32. package/templates/website-blog/styles/support.css +1 -0
  33. package/templates/website-blog/styles/terms.css +1 -0
  34. package/templates/website-business/InitialDataContext.tsx +21 -0
  35. package/templates/website-business/client.tsx +24 -0
  36. package/templates/website-business/{overrides/config.ts → config.ts} +6 -16
  37. package/templates/website-business/i18n/config.ts +43 -0
  38. package/templates/website-business/i18n/sections/common/_index.ts +5 -0
  39. package/templates/website-business/i18n/sections/common/en.ts +26 -0
  40. package/templates/website-business/i18n/sections/home/_index.ts +5 -0
  41. package/templates/website-business/i18n/sections/home/en.ts +7 -0
  42. package/templates/website-business/i18n/translations.ts +18 -0
  43. package/templates/website-business/index.ts +165 -0
  44. package/templates/website-business/pages/404.tsx +32 -0
  45. package/templates/website-business/pages/about.tsx +44 -0
  46. package/templates/website-business/pages/contact.tsx +45 -0
  47. package/templates/website-business/pages/index.tsx +64 -0
  48. package/templates/website-business/pages/privacy.tsx +35 -0
  49. package/templates/website-business/pages/services.tsx +62 -0
  50. package/templates/website-business/pages/support.tsx +40 -0
  51. package/templates/website-business/pages/terms.tsx +35 -0
  52. package/templates/website-business/rothzerg.template.json +52 -5
  53. package/templates/website-business/styles/404.css +1 -0
  54. package/templates/website-business/styles/_app.css +57 -0
  55. package/templates/website-business/styles/_shared.css +1 -0
  56. package/templates/website-business/styles/index.css +1 -0
  57. package/templates/website-business/styles/privacy.css +1 -0
  58. package/templates/website-business/styles/support.css +1 -0
  59. package/templates/website-business/styles/terms.css +1 -0
  60. package/templates/website-mobile/App.tsx +57 -0
  61. package/templates/website-mobile/InitialDataContext.tsx +21 -0
  62. package/templates/website-mobile/client.tsx +24 -0
  63. package/templates/website-mobile/{overrides/config.ts → config.ts} +4 -14
  64. package/templates/website-mobile/data/blog/authors.json +18 -0
  65. package/templates/website-mobile/data/blog/categories.json +19 -0
  66. package/templates/website-mobile/data/blog/posts.json +22 -0
  67. package/templates/website-mobile/data/blog/tags.json +16 -0
  68. package/templates/website-mobile/i18n/config.ts +43 -0
  69. package/templates/website-mobile/i18n/sections/common/_index.ts +5 -0
  70. package/templates/website-mobile/i18n/sections/common/en.ts +25 -0
  71. package/templates/website-mobile/i18n/sections/home/_index.ts +5 -0
  72. package/templates/website-mobile/i18n/sections/home/en.ts +8 -0
  73. package/templates/website-mobile/i18n/translations.ts +18 -0
  74. package/templates/website-mobile/index.ts +412 -0
  75. package/templates/website-mobile/pages/404.tsx +27 -0
  76. package/templates/website-mobile/pages/blog/author.tsx +48 -0
  77. package/templates/website-mobile/pages/blog/category.tsx +44 -0
  78. package/templates/website-mobile/pages/blog/index.tsx +51 -0
  79. package/templates/website-mobile/pages/blog/post.tsx +50 -0
  80. package/templates/website-mobile/pages/blog/tag.tsx +43 -0
  81. package/templates/website-mobile/pages/index.tsx +99 -0
  82. package/templates/website-mobile/pages/privacy.tsx +35 -0
  83. package/templates/website-mobile/pages/support.tsx +41 -0
  84. package/templates/website-mobile/pages/terms.tsx +35 -0
  85. package/templates/website-mobile/rothzerg.template.json +49 -9
  86. package/templates/website-mobile/styles/404.css +1 -0
  87. package/templates/website-mobile/styles/_app.css +58 -0
  88. package/templates/website-mobile/styles/_shared.css +1 -0
  89. package/templates/website-mobile/styles/index.css +1 -0
  90. package/templates/website-mobile/styles/privacy.css +1 -0
  91. package/templates/website-mobile/styles/support.css +1 -0
  92. package/templates/website-mobile/styles/terms.css +1 -0
  93. package/templates/_base-website/App.tsx +0 -59
  94. package/templates/_base-website/_gitignore +0 -5
  95. package/templates/_base-website/components/DeepLinkLayout.css +0 -41
  96. package/templates/_base-website/components/DeepLinkLayout.tsx +0 -39
  97. package/templates/_base-website/components/DownloadSection.css +0 -60
  98. package/templates/_base-website/components/DownloadSection.tsx +0 -61
  99. package/templates/_base-website/components/SiteFeaturesSection.tsx +0 -68
  100. package/templates/_base-website/components/SiteFooter.tsx +0 -29
  101. package/templates/_base-website/components/SiteHeroSection.tsx +0 -30
  102. package/templates/_base-website/components/SiteHighlightsSection.tsx +0 -38
  103. package/templates/_base-website/components/SiteLanguageSwitcher.tsx +0 -9
  104. package/templates/_base-website/components/SiteNavigation.tsx +0 -24
  105. package/templates/_base-website/config.ts +0 -78
  106. package/templates/_base-website/data/blog/authors.json +0 -18
  107. package/templates/_base-website/data/blog/categories.json +0 -53
  108. package/templates/_base-website/data/blog/posts.json +0 -29
  109. package/templates/_base-website/data/blog/tags.json +0 -86
  110. package/templates/_base-website/i18n/config.ts +0 -60
  111. package/templates/_base-website/i18n/sections/blog/_index.ts +0 -41
  112. package/templates/_base-website/i18n/sections/blog/ar.ts +0 -24
  113. package/templates/_base-website/i18n/sections/blog/de.ts +0 -24
  114. package/templates/_base-website/i18n/sections/blog/en.ts +0 -24
  115. package/templates/_base-website/i18n/sections/blog/es.ts +0 -24
  116. package/templates/_base-website/i18n/sections/blog/fr.ts +0 -24
  117. package/templates/_base-website/i18n/sections/blog/hi.ts +0 -24
  118. package/templates/_base-website/i18n/sections/blog/id.ts +0 -24
  119. package/templates/_base-website/i18n/sections/blog/it.ts +0 -24
  120. package/templates/_base-website/i18n/sections/blog/ja.ts +0 -24
  121. package/templates/_base-website/i18n/sections/blog/ko.ts +0 -24
  122. package/templates/_base-website/i18n/sections/blog/nl.ts +0 -24
  123. package/templates/_base-website/i18n/sections/blog/pl.ts +0 -24
  124. package/templates/_base-website/i18n/sections/blog/pt.ts +0 -24
  125. package/templates/_base-website/i18n/sections/blog/ru.ts +0 -24
  126. package/templates/_base-website/i18n/sections/blog/sv.ts +0 -24
  127. package/templates/_base-website/i18n/sections/blog/th.ts +0 -24
  128. package/templates/_base-website/i18n/sections/blog/tr.ts +0 -24
  129. package/templates/_base-website/i18n/sections/blog/vi.ts +0 -24
  130. package/templates/_base-website/i18n/sections/blog/zh.ts +0 -24
  131. package/templates/_base-website/i18n/sections/common/_index.ts +0 -41
  132. package/templates/_base-website/i18n/sections/common/ar.ts +0 -29
  133. package/templates/_base-website/i18n/sections/common/de.ts +0 -29
  134. package/templates/_base-website/i18n/sections/common/en.ts +0 -29
  135. package/templates/_base-website/i18n/sections/common/es.ts +0 -29
  136. package/templates/_base-website/i18n/sections/common/fr.ts +0 -29
  137. package/templates/_base-website/i18n/sections/common/hi.ts +0 -29
  138. package/templates/_base-website/i18n/sections/common/id.ts +0 -29
  139. package/templates/_base-website/i18n/sections/common/it.ts +0 -29
  140. package/templates/_base-website/i18n/sections/common/ja.ts +0 -29
  141. package/templates/_base-website/i18n/sections/common/ko.ts +0 -29
  142. package/templates/_base-website/i18n/sections/common/nl.ts +0 -29
  143. package/templates/_base-website/i18n/sections/common/pl.ts +0 -29
  144. package/templates/_base-website/i18n/sections/common/pt.ts +0 -29
  145. package/templates/_base-website/i18n/sections/common/ru.ts +0 -29
  146. package/templates/_base-website/i18n/sections/common/sv.ts +0 -29
  147. package/templates/_base-website/i18n/sections/common/th.ts +0 -29
  148. package/templates/_base-website/i18n/sections/common/tr.ts +0 -29
  149. package/templates/_base-website/i18n/sections/common/vi.ts +0 -29
  150. package/templates/_base-website/i18n/sections/common/zh.ts +0 -29
  151. package/templates/_base-website/i18n/sections/deepLink/_index.ts +0 -41
  152. package/templates/_base-website/i18n/sections/deepLink/ar.ts +0 -14
  153. package/templates/_base-website/i18n/sections/deepLink/de.ts +0 -14
  154. package/templates/_base-website/i18n/sections/deepLink/en.ts +0 -14
  155. package/templates/_base-website/i18n/sections/deepLink/es.ts +0 -14
  156. package/templates/_base-website/i18n/sections/deepLink/fr.ts +0 -14
  157. package/templates/_base-website/i18n/sections/deepLink/hi.ts +0 -14
  158. package/templates/_base-website/i18n/sections/deepLink/id.ts +0 -14
  159. package/templates/_base-website/i18n/sections/deepLink/it.ts +0 -14
  160. package/templates/_base-website/i18n/sections/deepLink/ja.ts +0 -14
  161. package/templates/_base-website/i18n/sections/deepLink/ko.ts +0 -14
  162. package/templates/_base-website/i18n/sections/deepLink/nl.ts +0 -14
  163. package/templates/_base-website/i18n/sections/deepLink/pl.ts +0 -14
  164. package/templates/_base-website/i18n/sections/deepLink/pt.ts +0 -14
  165. package/templates/_base-website/i18n/sections/deepLink/ru.ts +0 -14
  166. package/templates/_base-website/i18n/sections/deepLink/sv.ts +0 -14
  167. package/templates/_base-website/i18n/sections/deepLink/th.ts +0 -14
  168. package/templates/_base-website/i18n/sections/deepLink/tr.ts +0 -14
  169. package/templates/_base-website/i18n/sections/deepLink/vi.ts +0 -14
  170. package/templates/_base-website/i18n/sections/deepLink/zh.ts +0 -14
  171. package/templates/_base-website/i18n/sections/home/_index.ts +0 -41
  172. package/templates/_base-website/i18n/sections/home/ar.ts +0 -85
  173. package/templates/_base-website/i18n/sections/home/de.ts +0 -85
  174. package/templates/_base-website/i18n/sections/home/en.ts +0 -85
  175. package/templates/_base-website/i18n/sections/home/es.ts +0 -85
  176. package/templates/_base-website/i18n/sections/home/fr.ts +0 -85
  177. package/templates/_base-website/i18n/sections/home/hi.ts +0 -85
  178. package/templates/_base-website/i18n/sections/home/id.ts +0 -85
  179. package/templates/_base-website/i18n/sections/home/it.ts +0 -85
  180. package/templates/_base-website/i18n/sections/home/ja.ts +0 -85
  181. package/templates/_base-website/i18n/sections/home/ko.ts +0 -85
  182. package/templates/_base-website/i18n/sections/home/nl.ts +0 -85
  183. package/templates/_base-website/i18n/sections/home/pl.ts +0 -85
  184. package/templates/_base-website/i18n/sections/home/pt.ts +0 -85
  185. package/templates/_base-website/i18n/sections/home/ru.ts +0 -85
  186. package/templates/_base-website/i18n/sections/home/sv.ts +0 -85
  187. package/templates/_base-website/i18n/sections/home/th.ts +0 -85
  188. package/templates/_base-website/i18n/sections/home/tr.ts +0 -85
  189. package/templates/_base-website/i18n/sections/home/vi.ts +0 -85
  190. package/templates/_base-website/i18n/sections/home/zh.ts +0 -85
  191. package/templates/_base-website/i18n/sections/support/_index.ts +0 -41
  192. package/templates/_base-website/i18n/sections/support/ar.ts +0 -37
  193. package/templates/_base-website/i18n/sections/support/de.ts +0 -37
  194. package/templates/_base-website/i18n/sections/support/en.ts +0 -37
  195. package/templates/_base-website/i18n/sections/support/es.ts +0 -37
  196. package/templates/_base-website/i18n/sections/support/fr.ts +0 -37
  197. package/templates/_base-website/i18n/sections/support/hi.ts +0 -37
  198. package/templates/_base-website/i18n/sections/support/id.ts +0 -37
  199. package/templates/_base-website/i18n/sections/support/it.ts +0 -37
  200. package/templates/_base-website/i18n/sections/support/ja.ts +0 -37
  201. package/templates/_base-website/i18n/sections/support/ko.ts +0 -37
  202. package/templates/_base-website/i18n/sections/support/nl.ts +0 -37
  203. package/templates/_base-website/i18n/sections/support/pl.ts +0 -37
  204. package/templates/_base-website/i18n/sections/support/pt.ts +0 -37
  205. package/templates/_base-website/i18n/sections/support/ru.ts +0 -37
  206. package/templates/_base-website/i18n/sections/support/sv.ts +0 -37
  207. package/templates/_base-website/i18n/sections/support/th.ts +0 -37
  208. package/templates/_base-website/i18n/sections/support/tr.ts +0 -37
  209. package/templates/_base-website/i18n/sections/support/vi.ts +0 -37
  210. package/templates/_base-website/i18n/sections/support/zh.ts +0 -37
  211. package/templates/_base-website/i18n/translations.ts +0 -25
  212. package/templates/_base-website/pages/404.tsx +0 -35
  213. package/templates/_base-website/pages/blog/author.tsx +0 -97
  214. package/templates/_base-website/pages/blog/category.tsx +0 -89
  215. package/templates/_base-website/pages/blog/index.tsx +0 -81
  216. package/templates/_base-website/pages/blog/post.tsx +0 -110
  217. package/templates/_base-website/pages/blog/tag.tsx +0 -86
  218. package/templates/_base-website/pages/custom-pages/example.tsx +0 -54
  219. package/templates/_base-website/pages/index.tsx +0 -29
  220. package/templates/_base-website/pages/privacy.tsx +0 -45
  221. package/templates/_base-website/pages/support.tsx +0 -154
  222. package/templates/_base-website/pages/terms.tsx +0 -68
  223. package/templates/_base-website/public/images/16.png +0 -0
  224. package/templates/_base-website/public/images/apple-touch-icon.png +0 -0
  225. package/templates/_base-website/public/images/favicon-32x32.png +0 -0
  226. package/templates/_base-website/public/images/favicon.png +0 -0
  227. package/templates/_base-website/public/images/logo_dark.svg +0 -6
  228. package/templates/_base-website/public/images/logo_light.svg +0 -6
  229. package/templates/_base-website/public/images/og-image.png +0 -0
  230. package/templates/_base-website/public/images/screenshots/ar_dashboard.jpg +0 -0
  231. package/templates/_base-website/public/images/screenshots/de_dashboard.jpg +0 -0
  232. package/templates/_base-website/public/images/screenshots/en_dashboard.jpg +0 -0
  233. package/templates/_base-website/public/images/screenshots/es_dashboard.jpg +0 -0
  234. package/templates/_base-website/public/images/screenshots/fr_dashboard.jpg +0 -0
  235. package/templates/_base-website/public/images/screenshots/hi_dashboard.jpg +0 -0
  236. package/templates/_base-website/public/images/screenshots/id_dashboard.jpg +0 -0
  237. package/templates/_base-website/public/images/screenshots/it_dashboard.jpg +0 -0
  238. package/templates/_base-website/public/images/screenshots/ja_dashboard.jpg +0 -0
  239. package/templates/_base-website/public/images/screenshots/ko_dashboard.jpg +0 -0
  240. package/templates/_base-website/public/images/screenshots/nl_dashboard.jpg +0 -0
  241. package/templates/_base-website/public/images/screenshots/pl_dashboard.jpg +0 -0
  242. package/templates/_base-website/public/images/screenshots/pt_dashboard.jpg +0 -0
  243. package/templates/_base-website/public/images/screenshots/ru_dashboard.jpg +0 -0
  244. package/templates/_base-website/public/images/screenshots/sv_dashboard.jpg +0 -0
  245. package/templates/_base-website/public/images/screenshots/th_dashboard.jpg +0 -0
  246. package/templates/_base-website/public/images/screenshots/tr_dashboard.jpg +0 -0
  247. package/templates/_base-website/public/images/screenshots/vi_dashboard.jpg +0 -0
  248. package/templates/_base-website/public/images/screenshots/zh_dashboard.jpg +0 -0
  249. package/templates/_base-website/rothzerg.template.json +0 -84
  250. package/templates/_base-website/styles/404.css +0 -32
  251. package/templates/_base-website/styles/_app.css +0 -131
  252. package/templates/_base-website/styles/_shared.css +0 -194
  253. package/templates/_base-website/styles/index.css +0 -1
  254. package/templates/_base-website/styles/privacy.css +0 -1
  255. package/templates/_base-website/styles/support.css +0 -148
  256. package/templates/_base-website/styles/terms.css +0 -22
  257. package/templates/website-blog/overrides/pages/index.tsx +0 -63
  258. package/templates/website-business/overrides/components/SiteHeroSection.tsx +0 -33
  259. package/templates/website-business/overrides/pages/about.tsx +0 -50
  260. package/templates/website-business/overrides/pages/contact.tsx +0 -61
  261. package/templates/website-business/overrides/pages/index.tsx +0 -37
  262. package/templates/website-business/overrides/pages/services.tsx +0 -70
  263. package/templates/website-mobile/overrides/components/SiteHeroSection.tsx +0 -18
  264. package/templates/website-mobile/overrides/pages/index.tsx +0 -34
  265. /package/templates/{_base-website → website-blog}/InitialDataContext.tsx +0 -0
  266. /package/templates/website-business/{overrides/App.tsx → App.tsx} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rothzerg",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Plugin-style CLI engine for scaffolding and upgrading projects from config-driven, inheritable templates",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,6 @@ import { BlogPostPage } from "./pages/blog/post";
12
12
  import { BlogTagPage } from "./pages/blog/tag";
13
13
  import { BlogCategoryPage } from "./pages/blog/category";
14
14
  import { BlogAuthorPage } from "./pages/blog/author";
15
- import { DeepLinkLayout } from "./components/DeepLinkLayout";
16
15
  import { getI18nInstance, isValidLanguage } from "./i18n/config";
17
16
  import { DEFAULT_LANGUAGE, SITE_CONFIG } from "./config";
18
17
 
@@ -42,7 +41,6 @@ export function App() {
42
41
  <Route path="/:lang/terms" element={<LanguageWrapper><TermsPage /></LanguageWrapper>} />
43
42
  <Route path="/:lang/privacy" element={<LanguageWrapper><PrivacyPage /></LanguageWrapper>} />
44
43
  <Route path="/:lang/support" element={<LanguageWrapper><SupportPage /></LanguageWrapper>} />
45
- {/* Blog pages */}
46
44
  <Route path="/:lang/blog" element={<LanguageWrapper><BlogIndexPage /></LanguageWrapper>} />
47
45
  <Route path="/:lang/blog/page/:page" element={<LanguageWrapper><BlogIndexPage /></LanguageWrapper>} />
48
46
  <Route path="/:lang/blog/tag/:slug" element={<LanguageWrapper><BlogTagPage /></LanguageWrapper>} />
@@ -52,9 +50,6 @@ export function App() {
52
50
  <Route path="/:lang/blog/author/:slug" element={<LanguageWrapper><BlogAuthorPage /></LanguageWrapper>} />
53
51
  <Route path="/:lang/blog/author/:slug/page/:page" element={<LanguageWrapper><BlogAuthorPage /></LanguageWrapper>} />
54
52
  <Route path="/:lang/blog/:slug" element={<LanguageWrapper><BlogPostPage /></LanguageWrapper>} />
55
- {/* Deep link pages */}
56
- <Route path="/dl/:type/:id" element={<DeepLinkLayout />} />
57
- <Route path="/dl/:type" element={<DeepLinkLayout />} />
58
53
  <Route path="*" element={<LanguageWrapper><NotFoundPage /></LanguageWrapper>} />
59
54
  </Routes>
60
55
  </>
@@ -4,8 +4,6 @@ import { BrowserRouter } from "react-router-dom";
4
4
  import { App } from "./App.tsx";
5
5
  import { InitialDataProvider } from "./InitialDataContext.tsx";
6
6
 
7
- // Hydrate the app on the client side
8
- // @ts-ignore
9
7
  const rootElement = document.getElementById("root");
10
8
  if (rootElement) {
11
9
  // @ts-ignore
@@ -2,28 +2,18 @@ import { fetchAPI } from "../../utils";
2
2
 
3
3
  export const API_BASE_URL = "https://api.{{domain}}";
4
4
 
5
- export const SUPPORTED_LANGUAGES = [
6
- "en", "de", "fr", "it", "ja", "ko", "tr", "zh", "es", "pt",
7
- "hi", "ar", "ru", "pl", "nl", "id", "vi", "th", "sv",
8
- ] as const;
5
+ export const SUPPORTED_LANGUAGES = ["en"] as const;
9
6
 
10
7
  export type Language = typeof SUPPORTED_LANGUAGES[number];
11
8
  export const DEFAULT_LANGUAGE: Language = "en";
12
9
  export const APP_ADS_TXT = '';
13
10
 
14
11
  export const LANGUAGE_LOCALES: Record<Language, string> = {
15
- en: "en_US", de: "de_DE", fr: "fr_FR", it: "it_IT", ja: "ja_JP",
16
- ko: "ko_KR", tr: "tr_TR", zh: "zh_CN", es: "es_ES", pt: "pt_BR",
17
- hi: "hi_IN", ar: "ar_SA", ru: "ru_RU", pl: "pl_PL", nl: "nl_NL",
18
- id: "id_ID", vi: "vi_VN", th: "th_TH", sv: "sv_SE",
12
+ en: "en_US",
19
13
  };
20
14
 
21
15
  export const LANGUAGE_NAMES: Record<Language, string> = {
22
- en: "English", de: "Deutsch", fr: "Français", it: "Italiano",
23
- ja: "日本語", ko: "한국어", tr: "Türkçe", zh: "中文", es: "Español",
24
- pt: "Português", hi: "हिन्दी", ar: "العربية", ru: "Русский",
25
- pl: "Polski", nl: "Nederlands", id: "Indonesia", vi: "Tiếng Việt",
26
- th: "ไทย", sv: "Svenska",
16
+ en: "English",
27
17
  };
28
18
 
29
19
  export const SITE_CONFIG = {
@@ -0,0 +1,18 @@
1
+ [
2
+ {
3
+ "slug": "site-author",
4
+ "name": "{{siteName}}",
5
+ "avatar": "",
6
+ "bio": {
7
+ "en": "The team behind {{siteName}}."
8
+ },
9
+ "seo": {
10
+ "pageTitle": {
11
+ "en": "{{siteName}} — Author"
12
+ },
13
+ "metaDescription": {
14
+ "en": "Posts by {{siteName}}."
15
+ }
16
+ }
17
+ }
18
+ ]
@@ -0,0 +1,19 @@
1
+ [
2
+ {
3
+ "slug": "general",
4
+ "name": {
5
+ "en": "General"
6
+ },
7
+ "description": {
8
+ "en": "General posts and announcements."
9
+ },
10
+ "seo": {
11
+ "pageTitle": {
12
+ "en": "General Posts"
13
+ },
14
+ "metaDescription": {
15
+ "en": "General posts and announcements from {{siteName}}."
16
+ }
17
+ }
18
+ }
19
+ ]
@@ -0,0 +1,22 @@
1
+ [
2
+ {
3
+ "slug": "hello-world",
4
+ "title": {
5
+ "en": "Hello World — Getting Started with {{siteName}}"
6
+ },
7
+ "excerpt": {
8
+ "en": "Welcome to {{siteName}}! This is your first blog post. Edit this file to add your own content and start sharing your ideas with the world."
9
+ },
10
+ "content": {
11
+ "en": "<p>Welcome to <strong>{{siteName}}</strong>! This is your first blog post.</p>\n\n<h2>Getting Started</h2>\n<p>To add new posts, edit the <code>data/blog/posts.json</code> file in your website directory. Each post has a slug, title, excerpt, content, and optional metadata like author, category, and tags.</p>\n\n<h2>Next Steps</h2>\n<ul>\n <li>Update <code>config.ts</code> with your site details</li>\n <li>Add more posts to <code>data/blog/posts.json</code></li>\n <li>Add authors, categories, and tags to their respective JSON files</li>\n <li>Customize the styles in the <code>styles/</code> directory</li>\n <li>Add your translations in <code>i18n/sections/</code></li>\n</ul>\n\n<p>Happy blogging!</p>"
12
+ },
13
+ "coverImage": "https://picsum.photos/seed/hello-world/1200/630",
14
+ "authorSlug": "site-author",
15
+ "categorySlug": "general",
16
+ "tagSlugs": ["getting-started"],
17
+ "publishedAt": "{{builtIn:date}}T10:00:00Z",
18
+ "updatedAt": "{{builtIn:date}}T10:00:00Z",
19
+ "status": "published",
20
+ "readingTimeMinutes": 2
21
+ }
22
+ ]
@@ -0,0 +1,16 @@
1
+ [
2
+ {
3
+ "slug": "getting-started",
4
+ "name": {
5
+ "en": "Getting Started"
6
+ },
7
+ "seo": {
8
+ "pageTitle": {
9
+ "en": "Getting Started Posts"
10
+ },
11
+ "metaDescription": {
12
+ "en": "Getting started guides and introductory posts from {{siteName}}."
13
+ }
14
+ }
15
+ }
16
+ ]
@@ -0,0 +1,43 @@
1
+ import i18n, { createInstance } from "i18next";
2
+ import { initReactI18next } from "react-i18next";
3
+ import { translations } from "./translations";
4
+ import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from "../config";
5
+
6
+ export type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];
7
+
8
+ let clientInstance: typeof i18n | null = null;
9
+
10
+ export function getI18nInstance(language: SupportedLanguage = DEFAULT_LANGUAGE) {
11
+ // @ts-ignore
12
+ const isClient = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
13
+
14
+ if (isClient) {
15
+ if (!clientInstance) {
16
+ clientInstance = i18n.createInstance();
17
+ clientInstance.use(initReactI18next).init({
18
+ resources: translations,
19
+ lng: language,
20
+ fallbackLng: DEFAULT_LANGUAGE,
21
+ interpolation: { escapeValue: false },
22
+ react: { useSuspense: false },
23
+ showSupportNotice: false,
24
+ });
25
+ }
26
+ return clientInstance;
27
+ }
28
+
29
+ const serverInstance = createInstance();
30
+ serverInstance.use(initReactI18next).init({
31
+ resources: translations,
32
+ lng: language,
33
+ fallbackLng: DEFAULT_LANGUAGE,
34
+ interpolation: { escapeValue: false },
35
+ react: { useSuspense: false },
36
+ showSupportNotice: false,
37
+ });
38
+ return serverInstance;
39
+ }
40
+
41
+ export function isValidLanguage(lang: string): lang is SupportedLanguage {
42
+ return SUPPORTED_LANGUAGES.includes(lang as SupportedLanguage);
43
+ }
@@ -0,0 +1,5 @@
1
+ import { commonEN } from "./en";
2
+
3
+ export const commonTranslations: Record<string, any> = {
4
+ en: commonEN,
5
+ };
@@ -0,0 +1,24 @@
1
+ export const commonEN: Record<string, any> = {
2
+ meta: {
3
+ homeTitle: "Home",
4
+ homeDescription: "A blog by {{siteName}} — articles, tutorials, and insights.",
5
+ termsTitle: "Terms of Service",
6
+ termsDescription: "Read the {{siteName}} Terms of Service.",
7
+ privacyTitle: "Privacy Policy",
8
+ privacyDescription: "Learn how {{siteName}} collects and protects your information.",
9
+ supportTitle: "Support",
10
+ supportDescription: "Get help with {{siteName}}.",
11
+ },
12
+ nav: {
13
+ home: "Home",
14
+ blog: "Blog",
15
+ support: "Support",
16
+ },
17
+ footer: {
18
+ tagline: "Articles and insights",
19
+ legal: "Legal",
20
+ privacyPolicy: "Privacy Policy",
21
+ termsOfService: "Terms of Service",
22
+ copyright: "All rights reserved.",
23
+ },
24
+ };
@@ -0,0 +1,5 @@
1
+ import { homeEN } from "./en";
2
+
3
+ export const homeTranslations: Record<string, any> = {
4
+ en: homeEN,
5
+ };
@@ -0,0 +1,7 @@
1
+ export const homeEN: Record<string, any> = {
2
+ hero: {
3
+ title: "Hello from {{siteName}}",
4
+ subtitle: "A blog with articles, tutorials, and insights.",
5
+ cta: "Read the Blog",
6
+ },
7
+ };
@@ -0,0 +1,18 @@
1
+ // {{siteName}} translations
2
+ import { commonTranslations } from "./sections/common/_index";
3
+ import { homeTranslations } from "./sections/home/_index";
4
+
5
+ export const translationData: Record<string, any> = {};
6
+
7
+ const languages = Object.keys(commonTranslations);
8
+
9
+ for (const lang of languages) {
10
+ translationData[lang] = {
11
+ ...commonTranslations[lang],
12
+ ...homeTranslations[lang],
13
+ };
14
+ }
15
+
16
+ export const translations: Record<string, { translation: any }> = Object.fromEntries(
17
+ Object.entries(translationData).map(([lang, data]) => [lang, { translation: data }])
18
+ );
@@ -10,8 +10,7 @@ import { InitialDataProvider } from "./InitialDataContext.tsx";
10
10
  import { getRobotsTxt } from "../../utils/get-robots.ts";
11
11
  import { escapeHtml } from "../../utils/html-utils.ts";
12
12
  import { generateHreflangTags, generateSitemap } from "../../utils/seo-utils.ts";
13
- import { matchDeepLinkSegment } from "../../utils/routing-utils.ts";
14
- import { renderDeepLinkPage, renderNotFound } from "../../utils/render-utils.ts";
13
+ import { renderNotFound } from "../../utils/render-utils.ts";
15
14
  import {
16
15
  getBlogPostsWithDetails,
17
16
  getBlogPostWithDetails,
@@ -45,7 +44,6 @@ async function getBlogSidebar(lang: string) {
45
44
 
46
45
  const SITE_DIR = "{{domain}}";
47
46
 
48
- // Generate CSS and JS bundles at startup
49
47
  const cssInputPath = new URL("./styles/_app.css", import.meta.url).pathname;
50
48
  const outputDir = new URL("./public", import.meta.url).pathname;
51
49
 
@@ -82,7 +80,7 @@ function getPageMeta(pathname: string): {
82
80
  description = t.meta.supportDescription;
83
81
  } else if (page === "blog") {
84
82
  title = "Blog";
85
- description = `Articles, tutorials, and insights on building modern web applications from ${SITE_CONFIG.name}.`;
83
+ description = `Articles and insights from ${SITE_CONFIG.name}.`;
86
84
  }
87
85
 
88
86
  return {
@@ -106,7 +104,7 @@ function buildBaseHtml(
106
104
  ): string {
107
105
  const locale = LANGUAGE_LOCALES[lang] || "en_US";
108
106
  const canonicalUrl = `${SITE_CONFIG.url}${pathname}`;
109
- const isRTL = lang === "ar";
107
+ const isRTL = false;
110
108
  const safeTitle = escapeHtml(title);
111
109
  const safeDesc = escapeHtml(description);
112
110
  const imageUrl = ogImage || `${SITE_CONFIG.url}/static/images/og-image.png`;
@@ -124,7 +122,6 @@ function buildBaseHtml(
124
122
  <meta name="theme-color" content="${SITE_CONFIG.themeColor}">
125
123
  <link rel="canonical" href="${canonicalUrl}">
126
124
  ${generateHreflangTags(pathname, SUPPORTED_LANGUAGES, SITE_CONFIG.url, DEFAULT_LANGUAGE)}
127
- <!-- Open Graph -->
128
125
  <meta property="og:type" content="${ogType}">
129
126
  <meta property="og:title" content="${safeTitle}">
130
127
  <meta property="og:description" content="${safeDesc}">
@@ -132,17 +129,13 @@ ${generateHreflangTags(pathname, SUPPORTED_LANGUAGES, SITE_CONFIG.url, DEFAULT_L
132
129
  <meta property="og:site_name" content="${SITE_CONFIG.name}">
133
130
  <meta property="og:locale" content="${locale}">
134
131
  <meta property="og:image" content="${imageUrl}">
135
- <!-- Twitter Card -->
136
132
  <meta name="twitter:card" content="summary_large_image">
137
133
  <meta name="twitter:title" content="${safeTitle}">
138
134
  <meta name="twitter:description" content="${safeDesc}">
139
135
  <meta name="twitter:site" content="${SITE_CONFIG.twitterHandle}">
140
136
  <meta name="twitter:image" content="${imageUrl}">
141
- <!-- App Links -->
142
- <meta name="apple-itunes-app" content="app-id=${SITE_CONFIG.iosAppId}">
143
137
  ${extraHead}
144
- ${SITE_CONFIG.googleAnalyticsId ? ` <!-- Google Analytics -->
145
- <script async src="https://www.googletagmanager.com/gtag/js?id=${SITE_CONFIG.googleAnalyticsId}"></script>
138
+ ${SITE_CONFIG.googleAnalyticsId ? ` <script async src="https://www.googletagmanager.com/gtag/js?id=${SITE_CONFIG.googleAnalyticsId}"></script>
146
139
  <script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());gtag('config','${SITE_CONFIG.googleAnalyticsId}');</script>` : ""}
147
140
  <script>(function(){var t=localStorage.getItem('theme')||(window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light');document.documentElement.setAttribute('data-theme',t);})();</script>
148
141
  <style>
@@ -161,7 +154,6 @@ ${SITE_CONFIG.googleAnalyticsId ? ` <!-- Google Analytics -->
161
154
  </html>`;
162
155
  }
163
156
 
164
-
165
157
  export async function handleRequest(hostname: string, pathname: string = "/"): Promise<Response> {
166
158
  if (pathname === "/") {
167
159
  return new Response(null, {
@@ -170,9 +162,9 @@ export async function handleRequest(hostname: string, pathname: string = "/"): P
170
162
  });
171
163
  }
172
164
 
173
- // ── Blog API routes (/api/blog/:lang/...) ────────────────────────────────
165
+ // Blog API routes
174
166
  if (pathname.startsWith("/api/blog/")) {
175
- const parts = pathname.split("/").filter(Boolean); // ["api","blog",lang,resource?,id?]
167
+ const parts = pathname.split("/").filter(Boolean);
176
168
  const blogLang = parts[2];
177
169
  const resource = parts[3];
178
170
  const id = parts[4];
@@ -190,23 +182,19 @@ export async function handleRequest(hostname: string, pathname: string = "/"): P
190
182
  const posts = await getBlogPostsWithDetails(SITE_DIR, lang);
191
183
  return new Response(JSON.stringify(posts), { headers: jsonHeaders });
192
184
  }
193
-
194
185
  if (resource === "posts" && id) {
195
186
  const post = await getBlogPostWithDetails(SITE_DIR, lang, id);
196
187
  if (!post) return notFoundJson;
197
188
  return new Response(JSON.stringify(post), { headers: jsonHeaders });
198
189
  }
199
-
200
190
  if (resource === "categories") {
201
191
  const categories = await getBlogCategories(SITE_DIR, lang);
202
192
  return new Response(JSON.stringify(categories), { headers: jsonHeaders });
203
193
  }
204
-
205
194
  if (resource === "tags") {
206
195
  const tags = await getBlogTags(SITE_DIR, lang);
207
196
  return new Response(JSON.stringify(tags), { headers: jsonHeaders });
208
197
  }
209
-
210
198
  if (resource === "authors" && id) {
211
199
  const author = await getBlogAuthor(SITE_DIR, lang, id);
212
200
  if (!author) return notFoundJson;
@@ -216,7 +204,7 @@ export async function handleRequest(hostname: string, pathname: string = "/"): P
216
204
  return notFoundJson;
217
205
  }
218
206
 
219
- // ── Blog SSR pages (/:lang/blog and /:lang/blog/:slug) ───────────────────
207
+ // Blog SSR pages
220
208
  const pathParts = pathname.split("/").filter(Boolean);
221
209
  if (pathParts[1] === "blog" && isValidLanguage(pathParts[0])) {
222
210
  const pageLang = pathParts[0] as Language;
@@ -230,7 +218,6 @@ export async function handleRequest(hostname: string, pathname: string = "/"): P
230
218
  return { items, pagination: { page: safePage, totalPages, total: allItems.length, baseUrl: "" } };
231
219
  }
232
220
 
233
- // ── Tag / Category / Author filter pages ────────────────────────────────
234
221
  if (slug === "tag" && pathParts[3]) {
235
222
  const tagSlug = pathParts[3];
236
223
  const pageNum = pathParts[4] === "page" && pathParts[5] ? parseInt(pathParts[5], 10) || 1 : 1;
@@ -240,8 +227,6 @@ export async function handleRequest(hostname: string, pathname: string = "/"): P
240
227
  ]);
241
228
  const { items: posts, pagination } = paginate(allPosts, pageNum);
242
229
  pagination.baseUrl = `/${pageLang}/blog/tag/${tagSlug}`;
243
- const pageTitle = `${tag?.name ?? tagSlug} — Tag | ${SITE_CONFIG.name}`;
244
- const pageDesc = `Posts tagged "${tag?.name ?? tagSlug}" on ${SITE_CONFIG.name}.`;
245
230
  const data = { posts, tag, sidebar, pagination };
246
231
  const bodyHtml = renderToString(
247
232
  React.createElement(StaticRouter, { location: pathname },
@@ -251,7 +236,7 @@ export async function handleRequest(hostname: string, pathname: string = "/"): P
251
236
  )
252
237
  );
253
238
  return new Response(
254
- buildBaseHtml(pathname, pageLang, pageTitle, pageDesc, "website", bodyHtml, "", undefined, data),
239
+ buildBaseHtml(pathname, pageLang, `${tag?.name ?? tagSlug} — Tag | ${SITE_CONFIG.name}`, `Posts tagged "${tag?.name ?? tagSlug}".`, "website", bodyHtml, "", undefined, data),
255
240
  { headers: { "Content-Type": "text/html; charset=utf-8" } }
256
241
  );
257
242
  }
@@ -265,8 +250,6 @@ export async function handleRequest(hostname: string, pathname: string = "/"): P
265
250
  ]);
266
251
  const { items: posts, pagination } = paginate(allPosts, pageNum);
267
252
  pagination.baseUrl = `/${pageLang}/blog/category/${categorySlug}`;
268
- const pageTitle = `${category?.name ?? categorySlug} — Category | ${SITE_CONFIG.name}`;
269
- const pageDesc = category?.description ?? `Posts in category "${category?.name ?? categorySlug}" on ${SITE_CONFIG.name}.`;
270
253
  const data = { posts, category, sidebar, pagination };
271
254
  const bodyHtml = renderToString(
272
255
  React.createElement(StaticRouter, { location: pathname },
@@ -276,7 +259,7 @@ export async function handleRequest(hostname: string, pathname: string = "/"): P
276
259
  )
277
260
  );
278
261
  return new Response(
279
- buildBaseHtml(pathname, pageLang, pageTitle, pageDesc, "website", bodyHtml, "", undefined, data),
262
+ buildBaseHtml(pathname, pageLang, `${category?.name ?? categorySlug} — Category | ${SITE_CONFIG.name}`, category?.description ?? `Posts in ${category?.name ?? categorySlug}.`, "website", bodyHtml, "", undefined, data),
280
263
  { headers: { "Content-Type": "text/html; charset=utf-8" } }
281
264
  );
282
265
  }
@@ -290,8 +273,6 @@ export async function handleRequest(hostname: string, pathname: string = "/"): P
290
273
  ]);
291
274
  const { items: posts, pagination } = paginate(allPosts, pageNum);
292
275
  pagination.baseUrl = `/${pageLang}/blog/author/${authorSlug}`;
293
- const pageTitle = `${author?.name ?? authorSlug} — Author | ${SITE_CONFIG.name}`;
294
- const pageDesc = author?.bio?.substring(0, 160) ?? `Posts by ${author?.name ?? authorSlug} on ${SITE_CONFIG.name}.`;
295
276
  const data = { posts, author, sidebar, pagination };
296
277
  const bodyHtml = renderToString(
297
278
  React.createElement(StaticRouter, { location: pathname },
@@ -301,12 +282,11 @@ export async function handleRequest(hostname: string, pathname: string = "/"): P
301
282
  )
302
283
  );
303
284
  return new Response(
304
- buildBaseHtml(pathname, pageLang, pageTitle, pageDesc, "website", bodyHtml, "", undefined, data),
285
+ buildBaseHtml(pathname, pageLang, `${author?.name ?? authorSlug} — Author | ${SITE_CONFIG.name}`, author?.bio?.substring(0, 160) ?? `Posts by ${author?.name ?? authorSlug}.`, "website", bodyHtml, "", undefined, data),
305
286
  { headers: { "Content-Type": "text/html; charset=utf-8" } }
306
287
  );
307
288
  }
308
289
 
309
- // ── Blog listing with optional page: /page/:page ─────────────────────
310
290
  if (slug === "page" && pathParts[3]) {
311
291
  const pageNum = parseInt(pathParts[3], 10) || 1;
312
292
  if (pageNum === 1) {
@@ -318,8 +298,6 @@ export async function handleRequest(hostname: string, pathname: string = "/"): P
318
298
  ]);
319
299
  const { items: posts, pagination } = paginate(allPosts, pageNum);
320
300
  pagination.baseUrl = `/${pageLang}/blog`;
321
- const listTitle = `Blog — Page ${pageNum} | ${SITE_CONFIG.name}`;
322
- const listDesc = `Articles, tutorials, and insights on building modern web applications from ${SITE_CONFIG.name}.`;
323
301
  const data = { posts, sidebar, pagination };
324
302
  const bodyHtml = renderToString(
325
303
  React.createElement(StaticRouter, { location: pathname },
@@ -329,13 +307,12 @@ export async function handleRequest(hostname: string, pathname: string = "/"): P
329
307
  )
330
308
  );
331
309
  return new Response(
332
- buildBaseHtml(pathname, pageLang, listTitle, listDesc, "website", bodyHtml, "", undefined, data),
310
+ buildBaseHtml(pathname, pageLang, `Blog Page ${pageNum} | ${SITE_CONFIG.name}`, `Articles from ${SITE_CONFIG.name}.`, "website", bodyHtml, "", undefined, data),
333
311
  { headers: { "Content-Type": "text/html; charset=utf-8" } }
334
312
  );
335
313
  }
336
314
 
337
315
  if (slug) {
338
- // Single blog post page
339
316
  const [post, sidebar] = await Promise.all([
340
317
  getBlogPostWithDetails(SITE_DIR, pageLang, slug),
341
318
  getBlogSidebar(pageLang),
@@ -343,9 +320,6 @@ export async function handleRequest(hostname: string, pathname: string = "/"): P
343
320
  if (!post) {
344
321
  return renderNotFound(cssContent, clientName, SITE_CONFIG.name, StaticRouter, App);
345
322
  }
346
-
347
- const postTitle = `${escapeHtml(post.title)} | ${SITE_CONFIG.name}`;
348
- const postDesc = escapeHtml(post.excerpt.substring(0, 160));
349
323
  const jsonLd = JSON.stringify({
350
324
  "@context": "https://schema.org",
351
325
  "@type": "BlogPosting",
@@ -367,22 +341,19 @@ export async function handleRequest(hostname: string, pathname: string = "/"): P
367
341
  )
368
342
  )
369
343
  );
370
-
371
344
  return new Response(
372
- buildBaseHtml(pathname, pageLang, postTitle, postDesc, "article", bodyHtml, extraHead, post.coverImage || undefined, data),
345
+ buildBaseHtml(pathname, pageLang, `${escapeHtml(post.title)} | ${SITE_CONFIG.name}`, escapeHtml(post.excerpt.substring(0, 160)), "article", bodyHtml, extraHead, post.coverImage || undefined, data),
373
346
  { headers: { "Content-Type": "text/html; charset=utf-8" } }
374
347
  );
375
348
  }
376
349
 
377
- // Blog listing page (page 1)
350
+ // Blog listing (page 1)
378
351
  const [allPosts, sidebar] = await Promise.all([
379
352
  getBlogPostsWithDetails(SITE_DIR, pageLang),
380
353
  getBlogSidebar(pageLang),
381
354
  ]);
382
355
  const { items: posts, pagination } = paginate(allPosts, 1);
383
356
  pagination.baseUrl = `/${pageLang}/blog`;
384
- const listTitle = `Blog | ${SITE_CONFIG.name}`;
385
- const listDesc = `Articles, tutorials, and insights on building modern web applications from ${SITE_CONFIG.name}.`;
386
357
  const data = { posts, sidebar, pagination };
387
358
  const bodyHtml = renderToString(
388
359
  React.createElement(StaticRouter, { location: pathname },
@@ -391,27 +362,41 @@ export async function handleRequest(hostname: string, pathname: string = "/"): P
391
362
  )
392
363
  )
393
364
  );
365
+ return new Response(
366
+ buildBaseHtml(pathname, pageLang, `Blog | ${SITE_CONFIG.name}`, `Articles and insights from ${SITE_CONFIG.name}.`, "website", bodyHtml, "", undefined, data),
367
+ { headers: { "Content-Type": "text/html; charset=utf-8" } }
368
+ );
369
+ }
394
370
 
371
+ // Home page: inject recent posts
372
+ const pathParts2 = pathname.split("/").filter(Boolean);
373
+ if (pathParts2.length <= 1 && isValidLanguage(pathParts2[0])) {
374
+ const pageLang = pathParts2[0] as Language;
375
+ const allPosts = await getBlogPostsWithDetails(SITE_DIR, pageLang);
376
+ const posts = allPosts.slice(0, 3);
377
+ const data = { posts };
378
+ const { lang, title, description, ogType } = getPageMeta(pathname);
379
+ const bodyHtml = renderToString(
380
+ React.createElement(StaticRouter, { location: pathname },
381
+ React.createElement(InitialDataProvider, { data },
382
+ React.createElement(App)
383
+ )
384
+ )
385
+ );
395
386
  return new Response(
396
- buildBaseHtml(pathname, pageLang, listTitle, listDesc, "website", bodyHtml, "", undefined, data),
387
+ buildBaseHtml(pathname, lang, title, description, ogType, bodyHtml, "", undefined, data),
397
388
  { headers: { "Content-Type": "text/html; charset=utf-8" } }
398
389
  );
399
390
  }
400
391
 
401
392
  if (pathname === "/app-ads.txt") {
402
- return new Response(APP_ADS_TXT, {
403
- headers: {
404
- "Content-Type": "text/plain",
405
- },
406
- });
393
+ return new Response(APP_ADS_TXT, { headers: { "Content-Type": "text/plain" } });
407
394
  }
408
395
 
409
- // robots.txt
410
396
  if (pathname === "/robots.txt") {
411
- return getRobotsTxt(SITE_CONFIG.url, hostname.includes("dev."))
397
+ return getRobotsTxt(SITE_CONFIG.url, hostname.includes("dev."));
412
398
  }
413
399
 
414
- // sitemap.xml
415
400
  if (pathname === "/sitemap.xml") {
416
401
  const [posts, categories, tags, authors] = await Promise.all([
417
402
  getBlogPostsWithDetails(SITE_DIR, DEFAULT_LANGUAGE),
@@ -419,7 +404,6 @@ export async function handleRequest(hostname: string, pathname: string = "/"): P
419
404
  getBlogTags(SITE_DIR, DEFAULT_LANGUAGE),
420
405
  getBlogAuthors(SITE_DIR, DEFAULT_LANGUAGE),
421
406
  ]);
422
-
423
407
  const pages = [
424
408
  { path: "", priority: "1.0", changefreq: "weekly" },
425
409
  { path: "/blog", priority: "0.9", changefreq: "daily" },
@@ -427,32 +411,20 @@ export async function handleRequest(hostname: string, pathname: string = "/"): P
427
411
  ...categories.map((c) => ({ path: `/blog/category/${c.slug}`, priority: "0.6", changefreq: "weekly" })),
428
412
  ...tags.map((t) => ({ path: `/blog/tag/${t.slug}`, priority: "0.5", changefreq: "weekly" })),
429
413
  ...authors.map((a) => ({ path: `/blog/author/${a.slug}`, priority: "0.6", changefreq: "weekly" })),
430
- { path: "/support", priority: "0.8", changefreq: "monthly" },
414
+ { path: "/support", priority: "0.7", changefreq: "monthly" },
431
415
  { path: "/privacy", priority: "0.5", changefreq: "yearly" },
432
416
  { path: "/terms", priority: "0.5", changefreq: "yearly" },
433
417
  ];
434
-
435
- const sitemap = generateSitemap(
436
- pages,
437
- SUPPORTED_LANGUAGES,
438
- SITE_CONFIG.url,
439
- DEFAULT_LANGUAGE
440
- );
441
-
418
+ const sitemap = generateSitemap(pages, SUPPORTED_LANGUAGES, SITE_CONFIG.url, DEFAULT_LANGUAGE);
442
419
  return new Response(sitemap, {
443
- headers: {
444
- "Content-Type": "application/xml; charset=utf-8",
445
- "Cache-Control": "public, max-age=86400",
446
- },
420
+ headers: { "Content-Type": "application/xml; charset=utf-8", "Cache-Control": "public, max-age=86400" },
447
421
  });
448
422
  }
449
423
 
450
424
  const { lang, title, description, ogType } = getPageMeta(pathname);
451
-
452
425
  const bodyHtml = renderToString(
453
426
  React.createElement(StaticRouter, { location: pathname }, React.createElement(App))
454
427
  );
455
-
456
428
  return new Response(
457
429
  buildBaseHtml(pathname, lang, title, description, ogType, bodyHtml),
458
430
  { headers: { "Content-Type": "text/html; charset=utf-8" } }
@@ -0,0 +1,31 @@
1
+ import React from "react";
2
+ import { Link, useParams } from "react-router-dom";
3
+ import { SITE_CONFIG } from "../config";
4
+
5
+ export function NotFoundPage() {
6
+ const { lang } = useParams<{ lang: string }>();
7
+ const l = lang || "en";
8
+
9
+ return (
10
+ <div style={{ minHeight: "100vh", display: "flex", flexDirection: "column", backgroundColor: "var(--color-bg)", color: "var(--color-text)" }}>
11
+ <header style={{ padding: "1rem 2rem", borderBottom: "1px solid var(--color-border)", display: "flex", alignItems: "center", justifyContent: "space-between" }}>
12
+ <a href={`/${l}/`} style={{ fontWeight: 700, fontSize: "1.1rem", textDecoration: "none", color: "var(--color-text)" }}>{SITE_CONFIG.name}</a>
13
+ <nav style={{ display: "flex", gap: "1.5rem", fontSize: "0.9rem" }}>
14
+ <Link to={`/${l}/blog`} style={{ textDecoration: "none", color: "var(--color-text-muted)" }}>Blog</Link>
15
+ <Link to={`/${l}/support`} style={{ textDecoration: "none", color: "var(--color-text-muted)" }}>Support</Link>
16
+ </nav>
17
+ </header>
18
+ <main style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", padding: "4rem 2rem", textAlign: "center" }}>
19
+ <p style={{ fontSize: "5rem", fontWeight: 800, margin: "0 0 0.5rem", background: "linear-gradient(to right, var(--color-primary), var(--color-secondary))", WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent" }}>404</p>
20
+ <h1 style={{ fontSize: "1.75rem", fontWeight: 700, margin: "0 0 1rem" }}>Page not found</h1>
21
+ <p style={{ color: "var(--color-text-muted)", margin: "0 0 2rem" }}>The page you're looking for doesn't exist or has been moved.</p>
22
+ <Link to={`/${l}/`} style={{ padding: "0.75rem 1.5rem", background: "var(--color-primary)", color: "#fff", borderRadius: "0.5rem", textDecoration: "none", fontWeight: 600 }}>
23
+ Go Home
24
+ </Link>
25
+ </main>
26
+ <footer style={{ padding: "1.5rem 2rem", borderTop: "1px solid var(--color-border)", textAlign: "center", fontSize: "0.875rem", color: "var(--color-text-muted)" }}>
27
+ <p style={{ margin: 0 }}>&copy; {new Date().getFullYear()} {SITE_CONFIG.name}. All rights reserved.</p>
28
+ </footer>
29
+ </div>
30
+ );
31
+ }