rothzerg 0.1.3 → 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.
- package/package.json +1 -1
- package/templates/{website-mobile/overrides → website-blog}/App.tsx +0 -5
- package/templates/{_base-website → website-blog}/client.tsx +0 -2
- package/templates/website-blog/{overrides/config.ts → config.ts} +3 -13
- package/templates/website-blog/data/blog/authors.json +18 -0
- package/templates/website-blog/data/blog/categories.json +19 -0
- package/templates/website-blog/data/blog/posts.json +22 -0
- package/templates/website-blog/data/blog/tags.json +16 -0
- package/templates/website-blog/i18n/config.ts +43 -0
- package/templates/website-blog/i18n/sections/common/_index.ts +5 -0
- package/templates/website-blog/i18n/sections/common/en.ts +24 -0
- package/templates/website-blog/i18n/sections/home/_index.ts +5 -0
- package/templates/website-blog/i18n/sections/home/en.ts +7 -0
- package/templates/website-blog/i18n/translations.ts +18 -0
- package/templates/{_base-website → website-blog}/index.ts +39 -67
- package/templates/website-blog/pages/404.tsx +31 -0
- package/templates/website-blog/pages/blog/author.tsx +53 -0
- package/templates/website-blog/pages/blog/category.tsx +52 -0
- package/templates/website-blog/pages/blog/index.tsx +59 -0
- package/templates/website-blog/pages/blog/post.tsx +87 -0
- package/templates/website-blog/pages/blog/tag.tsx +49 -0
- package/templates/website-blog/pages/index.tsx +78 -0
- package/templates/website-blog/pages/privacy.tsx +45 -0
- package/templates/website-blog/pages/support.tsx +48 -0
- package/templates/website-blog/pages/terms.tsx +45 -0
- package/templates/website-blog/rothzerg.template.json +52 -5
- package/templates/website-blog/styles/404.css +1 -0
- package/templates/website-blog/styles/_app.css +116 -0
- package/templates/website-blog/styles/_shared.css +1 -0
- package/templates/website-blog/styles/index.css +1 -0
- package/templates/website-blog/styles/privacy.css +1 -0
- package/templates/website-blog/styles/support.css +1 -0
- package/templates/website-blog/styles/terms.css +1 -0
- package/templates/website-business/InitialDataContext.tsx +21 -0
- package/templates/website-business/client.tsx +24 -0
- package/templates/website-business/{overrides/config.ts → config.ts} +6 -16
- package/templates/website-business/i18n/config.ts +43 -0
- package/templates/website-business/i18n/sections/common/_index.ts +5 -0
- package/templates/website-business/i18n/sections/common/en.ts +26 -0
- package/templates/website-business/i18n/sections/home/_index.ts +5 -0
- package/templates/website-business/i18n/sections/home/en.ts +7 -0
- package/templates/website-business/i18n/translations.ts +18 -0
- package/templates/website-business/index.ts +165 -0
- package/templates/website-business/pages/404.tsx +32 -0
- package/templates/website-business/pages/about.tsx +44 -0
- package/templates/website-business/pages/contact.tsx +45 -0
- package/templates/website-business/pages/index.tsx +64 -0
- package/templates/website-business/pages/privacy.tsx +35 -0
- package/templates/website-business/pages/services.tsx +62 -0
- package/templates/website-business/pages/support.tsx +40 -0
- package/templates/website-business/pages/terms.tsx +35 -0
- package/templates/website-business/rothzerg.template.json +52 -5
- package/templates/website-business/styles/404.css +1 -0
- package/templates/website-business/styles/_app.css +57 -0
- package/templates/website-business/styles/_shared.css +1 -0
- package/templates/website-business/styles/index.css +1 -0
- package/templates/website-business/styles/privacy.css +1 -0
- package/templates/website-business/styles/support.css +1 -0
- package/templates/website-business/styles/terms.css +1 -0
- package/templates/website-mobile/App.tsx +57 -0
- package/templates/website-mobile/InitialDataContext.tsx +21 -0
- package/templates/website-mobile/client.tsx +24 -0
- package/templates/website-mobile/{overrides/config.ts → config.ts} +4 -14
- package/templates/website-mobile/data/blog/authors.json +18 -0
- package/templates/website-mobile/data/blog/categories.json +19 -0
- package/templates/website-mobile/data/blog/posts.json +22 -0
- package/templates/website-mobile/data/blog/tags.json +16 -0
- package/templates/website-mobile/i18n/config.ts +43 -0
- package/templates/website-mobile/i18n/sections/common/_index.ts +5 -0
- package/templates/website-mobile/i18n/sections/common/en.ts +25 -0
- package/templates/website-mobile/i18n/sections/home/_index.ts +5 -0
- package/templates/website-mobile/i18n/sections/home/en.ts +8 -0
- package/templates/website-mobile/i18n/translations.ts +18 -0
- package/templates/website-mobile/index.ts +412 -0
- package/templates/website-mobile/pages/404.tsx +27 -0
- package/templates/website-mobile/pages/blog/author.tsx +48 -0
- package/templates/website-mobile/pages/blog/category.tsx +44 -0
- package/templates/website-mobile/pages/blog/index.tsx +51 -0
- package/templates/website-mobile/pages/blog/post.tsx +50 -0
- package/templates/website-mobile/pages/blog/tag.tsx +43 -0
- package/templates/website-mobile/pages/index.tsx +99 -0
- package/templates/website-mobile/pages/privacy.tsx +35 -0
- package/templates/website-mobile/pages/support.tsx +41 -0
- package/templates/website-mobile/pages/terms.tsx +35 -0
- package/templates/website-mobile/rothzerg.template.json +49 -9
- package/templates/website-mobile/styles/404.css +1 -0
- package/templates/website-mobile/styles/_app.css +58 -0
- package/templates/website-mobile/styles/_shared.css +1 -0
- package/templates/website-mobile/styles/index.css +1 -0
- package/templates/website-mobile/styles/privacy.css +1 -0
- package/templates/website-mobile/styles/support.css +1 -0
- package/templates/website-mobile/styles/terms.css +1 -0
- package/templates/_base-website/App.tsx +0 -59
- package/templates/_base-website/_gitignore +0 -5
- package/templates/_base-website/components/DeepLinkLayout.css +0 -41
- package/templates/_base-website/components/DeepLinkLayout.tsx +0 -39
- package/templates/_base-website/components/DownloadSection.css +0 -60
- package/templates/_base-website/components/DownloadSection.tsx +0 -61
- package/templates/_base-website/components/SiteFeaturesSection.tsx +0 -68
- package/templates/_base-website/components/SiteFooter.tsx +0 -29
- package/templates/_base-website/components/SiteHeroSection.tsx +0 -30
- package/templates/_base-website/components/SiteHighlightsSection.tsx +0 -38
- package/templates/_base-website/components/SiteLanguageSwitcher.tsx +0 -9
- package/templates/_base-website/components/SiteNavigation.tsx +0 -24
- package/templates/_base-website/config.ts +0 -78
- package/templates/_base-website/data/blog/authors.json +0 -18
- package/templates/_base-website/data/blog/categories.json +0 -53
- package/templates/_base-website/data/blog/posts.json +0 -29
- package/templates/_base-website/data/blog/tags.json +0 -86
- package/templates/_base-website/i18n/config.ts +0 -60
- package/templates/_base-website/i18n/sections/blog/_index.ts +0 -41
- package/templates/_base-website/i18n/sections/blog/ar.ts +0 -24
- package/templates/_base-website/i18n/sections/blog/de.ts +0 -24
- package/templates/_base-website/i18n/sections/blog/en.ts +0 -24
- package/templates/_base-website/i18n/sections/blog/es.ts +0 -24
- package/templates/_base-website/i18n/sections/blog/fr.ts +0 -24
- package/templates/_base-website/i18n/sections/blog/hi.ts +0 -24
- package/templates/_base-website/i18n/sections/blog/id.ts +0 -24
- package/templates/_base-website/i18n/sections/blog/it.ts +0 -24
- package/templates/_base-website/i18n/sections/blog/ja.ts +0 -24
- package/templates/_base-website/i18n/sections/blog/ko.ts +0 -24
- package/templates/_base-website/i18n/sections/blog/nl.ts +0 -24
- package/templates/_base-website/i18n/sections/blog/pl.ts +0 -24
- package/templates/_base-website/i18n/sections/blog/pt.ts +0 -24
- package/templates/_base-website/i18n/sections/blog/ru.ts +0 -24
- package/templates/_base-website/i18n/sections/blog/sv.ts +0 -24
- package/templates/_base-website/i18n/sections/blog/th.ts +0 -24
- package/templates/_base-website/i18n/sections/blog/tr.ts +0 -24
- package/templates/_base-website/i18n/sections/blog/vi.ts +0 -24
- package/templates/_base-website/i18n/sections/blog/zh.ts +0 -24
- package/templates/_base-website/i18n/sections/common/_index.ts +0 -41
- package/templates/_base-website/i18n/sections/common/ar.ts +0 -29
- package/templates/_base-website/i18n/sections/common/de.ts +0 -29
- package/templates/_base-website/i18n/sections/common/en.ts +0 -29
- package/templates/_base-website/i18n/sections/common/es.ts +0 -29
- package/templates/_base-website/i18n/sections/common/fr.ts +0 -29
- package/templates/_base-website/i18n/sections/common/hi.ts +0 -29
- package/templates/_base-website/i18n/sections/common/id.ts +0 -29
- package/templates/_base-website/i18n/sections/common/it.ts +0 -29
- package/templates/_base-website/i18n/sections/common/ja.ts +0 -29
- package/templates/_base-website/i18n/sections/common/ko.ts +0 -29
- package/templates/_base-website/i18n/sections/common/nl.ts +0 -29
- package/templates/_base-website/i18n/sections/common/pl.ts +0 -29
- package/templates/_base-website/i18n/sections/common/pt.ts +0 -29
- package/templates/_base-website/i18n/sections/common/ru.ts +0 -29
- package/templates/_base-website/i18n/sections/common/sv.ts +0 -29
- package/templates/_base-website/i18n/sections/common/th.ts +0 -29
- package/templates/_base-website/i18n/sections/common/tr.ts +0 -29
- package/templates/_base-website/i18n/sections/common/vi.ts +0 -29
- package/templates/_base-website/i18n/sections/common/zh.ts +0 -29
- package/templates/_base-website/i18n/sections/deepLink/_index.ts +0 -41
- package/templates/_base-website/i18n/sections/deepLink/ar.ts +0 -14
- package/templates/_base-website/i18n/sections/deepLink/de.ts +0 -14
- package/templates/_base-website/i18n/sections/deepLink/en.ts +0 -14
- package/templates/_base-website/i18n/sections/deepLink/es.ts +0 -14
- package/templates/_base-website/i18n/sections/deepLink/fr.ts +0 -14
- package/templates/_base-website/i18n/sections/deepLink/hi.ts +0 -14
- package/templates/_base-website/i18n/sections/deepLink/id.ts +0 -14
- package/templates/_base-website/i18n/sections/deepLink/it.ts +0 -14
- package/templates/_base-website/i18n/sections/deepLink/ja.ts +0 -14
- package/templates/_base-website/i18n/sections/deepLink/ko.ts +0 -14
- package/templates/_base-website/i18n/sections/deepLink/nl.ts +0 -14
- package/templates/_base-website/i18n/sections/deepLink/pl.ts +0 -14
- package/templates/_base-website/i18n/sections/deepLink/pt.ts +0 -14
- package/templates/_base-website/i18n/sections/deepLink/ru.ts +0 -14
- package/templates/_base-website/i18n/sections/deepLink/sv.ts +0 -14
- package/templates/_base-website/i18n/sections/deepLink/th.ts +0 -14
- package/templates/_base-website/i18n/sections/deepLink/tr.ts +0 -14
- package/templates/_base-website/i18n/sections/deepLink/vi.ts +0 -14
- package/templates/_base-website/i18n/sections/deepLink/zh.ts +0 -14
- package/templates/_base-website/i18n/sections/home/_index.ts +0 -41
- package/templates/_base-website/i18n/sections/home/ar.ts +0 -85
- package/templates/_base-website/i18n/sections/home/de.ts +0 -85
- package/templates/_base-website/i18n/sections/home/en.ts +0 -85
- package/templates/_base-website/i18n/sections/home/es.ts +0 -85
- package/templates/_base-website/i18n/sections/home/fr.ts +0 -85
- package/templates/_base-website/i18n/sections/home/hi.ts +0 -85
- package/templates/_base-website/i18n/sections/home/id.ts +0 -85
- package/templates/_base-website/i18n/sections/home/it.ts +0 -85
- package/templates/_base-website/i18n/sections/home/ja.ts +0 -85
- package/templates/_base-website/i18n/sections/home/ko.ts +0 -85
- package/templates/_base-website/i18n/sections/home/nl.ts +0 -85
- package/templates/_base-website/i18n/sections/home/pl.ts +0 -85
- package/templates/_base-website/i18n/sections/home/pt.ts +0 -85
- package/templates/_base-website/i18n/sections/home/ru.ts +0 -85
- package/templates/_base-website/i18n/sections/home/sv.ts +0 -85
- package/templates/_base-website/i18n/sections/home/th.ts +0 -85
- package/templates/_base-website/i18n/sections/home/tr.ts +0 -85
- package/templates/_base-website/i18n/sections/home/vi.ts +0 -85
- package/templates/_base-website/i18n/sections/home/zh.ts +0 -85
- package/templates/_base-website/i18n/sections/support/_index.ts +0 -41
- package/templates/_base-website/i18n/sections/support/ar.ts +0 -37
- package/templates/_base-website/i18n/sections/support/de.ts +0 -37
- package/templates/_base-website/i18n/sections/support/en.ts +0 -37
- package/templates/_base-website/i18n/sections/support/es.ts +0 -37
- package/templates/_base-website/i18n/sections/support/fr.ts +0 -37
- package/templates/_base-website/i18n/sections/support/hi.ts +0 -37
- package/templates/_base-website/i18n/sections/support/id.ts +0 -37
- package/templates/_base-website/i18n/sections/support/it.ts +0 -37
- package/templates/_base-website/i18n/sections/support/ja.ts +0 -37
- package/templates/_base-website/i18n/sections/support/ko.ts +0 -37
- package/templates/_base-website/i18n/sections/support/nl.ts +0 -37
- package/templates/_base-website/i18n/sections/support/pl.ts +0 -37
- package/templates/_base-website/i18n/sections/support/pt.ts +0 -37
- package/templates/_base-website/i18n/sections/support/ru.ts +0 -37
- package/templates/_base-website/i18n/sections/support/sv.ts +0 -37
- package/templates/_base-website/i18n/sections/support/th.ts +0 -37
- package/templates/_base-website/i18n/sections/support/tr.ts +0 -37
- package/templates/_base-website/i18n/sections/support/vi.ts +0 -37
- package/templates/_base-website/i18n/sections/support/zh.ts +0 -37
- package/templates/_base-website/i18n/translations.ts +0 -25
- package/templates/_base-website/pages/404.tsx +0 -35
- package/templates/_base-website/pages/blog/author.tsx +0 -97
- package/templates/_base-website/pages/blog/category.tsx +0 -89
- package/templates/_base-website/pages/blog/index.tsx +0 -81
- package/templates/_base-website/pages/blog/post.tsx +0 -110
- package/templates/_base-website/pages/blog/tag.tsx +0 -86
- package/templates/_base-website/pages/custom-pages/example.tsx +0 -54
- package/templates/_base-website/pages/index.tsx +0 -29
- package/templates/_base-website/pages/privacy.tsx +0 -45
- package/templates/_base-website/pages/support.tsx +0 -154
- package/templates/_base-website/pages/terms.tsx +0 -68
- package/templates/_base-website/public/images/16.png +0 -0
- package/templates/_base-website/public/images/apple-touch-icon.png +0 -0
- package/templates/_base-website/public/images/favicon-32x32.png +0 -0
- package/templates/_base-website/public/images/favicon.png +0 -0
- package/templates/_base-website/public/images/logo_dark.svg +0 -6
- package/templates/_base-website/public/images/logo_light.svg +0 -6
- package/templates/_base-website/public/images/og-image.png +0 -0
- package/templates/_base-website/public/images/screenshots/ar_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/de_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/en_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/es_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/fr_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/hi_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/id_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/it_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/ja_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/ko_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/nl_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/pl_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/pt_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/ru_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/sv_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/th_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/tr_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/vi_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/zh_dashboard.jpg +0 -0
- package/templates/_base-website/rothzerg.template.json +0 -86
- package/templates/_base-website/styles/404.css +0 -32
- package/templates/_base-website/styles/_app.css +0 -131
- package/templates/_base-website/styles/_shared.css +0 -194
- package/templates/_base-website/styles/index.css +0 -1
- package/templates/_base-website/styles/privacy.css +0 -1
- package/templates/_base-website/styles/support.css +0 -148
- package/templates/_base-website/styles/terms.css +0 -22
- package/templates/website-blog/overrides/pages/index.tsx +0 -63
- package/templates/website-business/overrides/components/SiteHeroSection.tsx +0 -33
- package/templates/website-business/overrides/pages/about.tsx +0 -50
- package/templates/website-business/overrides/pages/contact.tsx +0 -61
- package/templates/website-business/overrides/pages/index.tsx +0 -37
- package/templates/website-business/overrides/pages/services.tsx +0 -70
- package/templates/website-mobile/overrides/components/SiteHeroSection.tsx +0 -18
- package/templates/website-mobile/overrides/pages/index.tsx +0 -34
- /package/templates/{_base-website → website-blog}/InitialDataContext.tsx +0 -0
- /package/templates/website-business/{overrides/App.tsx → App.tsx} +0 -0
package/package.json
CHANGED
|
@@ -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",
|
|
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",
|
|
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,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,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 {
|
|
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
|
|
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 =
|
|
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 ? `
|
|
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
|
-
//
|
|
165
|
+
// Blog API routes
|
|
174
166
|
if (pathname.startsWith("/api/blog/")) {
|
|
175
|
-
const parts = pathname.split("/").filter(Boolean);
|
|
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
|
-
//
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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.
|
|
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 }}>© {new Date().getFullYear()} {SITE_CONFIG.name}. All rights reserved.</p>
|
|
28
|
+
</footer>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|