@parche/core 0.3.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/package.json +55 -0
- package/src/components/DynamicRenderer.astro +96 -0
- package/src/components/LayoutRenderer.astro +65 -0
- package/src/components/SectionWrapper.astro +46 -0
- package/src/components/common/LocaleSwitcher.astro +168 -0
- package/src/components/common/OptimizedImage.astro +41 -0
- package/src/components/common/ThemePanel.astro +152 -0
- package/src/components/common/ThemeSelector.astro +67 -0
- package/src/components/common/ThemeToggle.astro +82 -0
- package/src/config/font-variables.ts +29 -0
- package/src/config/fonts.ts +18 -0
- package/src/content/index.ts +11 -0
- package/src/content/schemas.ts +253 -0
- package/src/integration/index.ts +156 -0
- package/src/integration/registry.ts +193 -0
- package/src/integration/types.ts +193 -0
- package/src/integration/virtual.d.ts +326 -0
- package/src/integration/vite-plugin-parche.ts +432 -0
- package/src/layouts/BaseLayout.astro +121 -0
- package/src/routes/404.astro +46 -0
- package/src/routes/[...slug].astro +216 -0
- package/src/routes/middleware.ts +18 -0
- package/src/styles/base.css +221 -0
- package/src/styles/semantic.css +95 -0
- package/src/styles/shadcn-compat.css +120 -0
- package/src/styles/tokens.css +237 -0
- package/src/types/config.ts +102 -0
- package/src/utils/i18n.ts +79 -0
- package/src/utils/layout.ts +46 -0
- package/src/utils/metadata.ts +277 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/* =================================================================
|
|
2
|
+
Capa 1: Tokens primitivos
|
|
3
|
+
Valores base como CSS custom properties (siempre disponibles).
|
|
4
|
+
Colores en OKLCH para wide gamut y consistencia perceptual.
|
|
5
|
+
================================================================= */
|
|
6
|
+
|
|
7
|
+
/* ---------------------------------------------------------------
|
|
8
|
+
Primitive values — always available as CSS vars
|
|
9
|
+
--------------------------------------------------------------- */
|
|
10
|
+
:root {
|
|
11
|
+
/* Neutral (hue 260 — slate-blue tint, AstroWind v1 palette) */
|
|
12
|
+
--color-neutral-50: oklch(0.985 0.002 260);
|
|
13
|
+
--color-neutral-100: oklch(0.965 0.005 260);
|
|
14
|
+
--color-neutral-200: oklch(0.925 0.008 260);
|
|
15
|
+
--color-neutral-300: oklch(0.860 0.014 260);
|
|
16
|
+
--color-neutral-400: oklch(0.700 0.025 260);
|
|
17
|
+
--color-neutral-500: oklch(0.550 0.025 260);
|
|
18
|
+
--color-neutral-600: oklch(0.450 0.025 260);
|
|
19
|
+
--color-neutral-700: oklch(0.370 0.020 260);
|
|
20
|
+
--color-neutral-800: oklch(0.250 0.025 260);
|
|
21
|
+
--color-neutral-900: oklch(0.180 0.030 260);
|
|
22
|
+
--color-neutral-950: oklch(0.100 0.040 260);
|
|
23
|
+
|
|
24
|
+
/* Primary (hue 260 — vivid blue, AstroWind v1: rgb(1 97 239)) */
|
|
25
|
+
--color-primary-50: oklch(0.970 0.025 255);
|
|
26
|
+
--color-primary-100: oklch(0.940 0.050 255);
|
|
27
|
+
--color-primary-200: oklch(0.880 0.100 255);
|
|
28
|
+
--color-primary-300: oklch(0.780 0.155 255);
|
|
29
|
+
--color-primary-400: oklch(0.660 0.200 258);
|
|
30
|
+
--color-primary-500: oklch(0.560 0.220 260);
|
|
31
|
+
--color-primary-600: oklch(0.500 0.220 260);
|
|
32
|
+
--color-primary-700: oklch(0.430 0.190 260);
|
|
33
|
+
--color-primary-800: oklch(0.370 0.160 260);
|
|
34
|
+
--color-primary-900: oklch(0.310 0.130 260);
|
|
35
|
+
--color-primary-950: oklch(0.220 0.100 260);
|
|
36
|
+
|
|
37
|
+
/* Secondary (hue 280 — indigo/violet) */
|
|
38
|
+
--color-secondary-50: oklch(0.97 0.014 280);
|
|
39
|
+
--color-secondary-100: oklch(0.943 0.029 280);
|
|
40
|
+
--color-secondary-200: oklch(0.894 0.055 280);
|
|
41
|
+
--color-secondary-300: oklch(0.821 0.098 280);
|
|
42
|
+
--color-secondary-400: oklch(0.714 0.163 280);
|
|
43
|
+
--color-secondary-500: oklch(0.627 0.21 280);
|
|
44
|
+
--color-secondary-600: oklch(0.558 0.233 280);
|
|
45
|
+
--color-secondary-700: oklch(0.496 0.224 280);
|
|
46
|
+
--color-secondary-800: oklch(0.432 0.187 280);
|
|
47
|
+
--color-secondary-900: oklch(0.381 0.143 280);
|
|
48
|
+
--color-secondary-950: oklch(0.271 0.116 280);
|
|
49
|
+
|
|
50
|
+
/* Accent (hue 85 — amber/yellow-green) */
|
|
51
|
+
--color-accent-50: oklch(0.987 0.022 85);
|
|
52
|
+
--color-accent-100: oklch(0.968 0.047 85);
|
|
53
|
+
--color-accent-200: oklch(0.939 0.089 85);
|
|
54
|
+
--color-accent-300: oklch(0.897 0.138 85);
|
|
55
|
+
--color-accent-400: oklch(0.843 0.17 85);
|
|
56
|
+
--color-accent-500: oklch(0.776 0.174 85);
|
|
57
|
+
--color-accent-600: oklch(0.666 0.16 85);
|
|
58
|
+
--color-accent-700: oklch(0.555 0.13 85);
|
|
59
|
+
--color-accent-800: oklch(0.466 0.106 85);
|
|
60
|
+
--color-accent-900: oklch(0.398 0.086 85);
|
|
61
|
+
--color-accent-950: oklch(0.286 0.066 85);
|
|
62
|
+
|
|
63
|
+
/* Success (hue 145 — green) */
|
|
64
|
+
--color-success-50: oklch(0.982 0.018 145);
|
|
65
|
+
--color-success-100: oklch(0.955 0.04 145);
|
|
66
|
+
--color-success-200: oklch(0.905 0.093 145);
|
|
67
|
+
--color-success-300: oklch(0.845 0.143 145);
|
|
68
|
+
--color-success-400: oklch(0.765 0.177 145);
|
|
69
|
+
--color-success-500: oklch(0.672 0.176 145);
|
|
70
|
+
--color-success-600: oklch(0.565 0.155 145);
|
|
71
|
+
--color-success-700: oklch(0.479 0.13 145);
|
|
72
|
+
--color-success-800: oklch(0.405 0.106 145);
|
|
73
|
+
--color-success-900: oklch(0.353 0.085 145);
|
|
74
|
+
--color-success-950: oklch(0.246 0.064 145);
|
|
75
|
+
|
|
76
|
+
/* Warning (hue 70 — orange/amber) */
|
|
77
|
+
--color-warning-50: oklch(0.987 0.022 70);
|
|
78
|
+
--color-warning-100: oklch(0.962 0.054 70);
|
|
79
|
+
--color-warning-200: oklch(0.924 0.107 70);
|
|
80
|
+
--color-warning-300: oklch(0.879 0.15 70);
|
|
81
|
+
--color-warning-400: oklch(0.828 0.168 70);
|
|
82
|
+
--color-warning-500: oklch(0.769 0.163 70);
|
|
83
|
+
--color-warning-600: oklch(0.666 0.153 70);
|
|
84
|
+
--color-warning-700: oklch(0.555 0.128 70);
|
|
85
|
+
--color-warning-800: oklch(0.466 0.106 70);
|
|
86
|
+
--color-warning-900: oklch(0.398 0.086 70);
|
|
87
|
+
--color-warning-950: oklch(0.286 0.066 70);
|
|
88
|
+
|
|
89
|
+
/* Danger (hue 25 — red) */
|
|
90
|
+
--color-danger-50: oklch(0.977 0.013 25);
|
|
91
|
+
--color-danger-100: oklch(0.946 0.033 25);
|
|
92
|
+
--color-danger-200: oklch(0.895 0.064 25);
|
|
93
|
+
--color-danger-300: oklch(0.832 0.111 25);
|
|
94
|
+
--color-danger-400: oklch(0.744 0.16 25);
|
|
95
|
+
--color-danger-500: oklch(0.655 0.195 25);
|
|
96
|
+
--color-danger-600: oklch(0.577 0.215 25);
|
|
97
|
+
--color-danger-700: oklch(0.505 0.206 25);
|
|
98
|
+
--color-danger-800: oklch(0.444 0.177 25);
|
|
99
|
+
--color-danger-900: oklch(0.396 0.141 25);
|
|
100
|
+
--color-danger-950: oklch(0.278 0.109 25);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* ---------------------------------------------------------------
|
|
104
|
+
Bridge primitives into Tailwind utility classes via @theme inline.
|
|
105
|
+
Generates: bg-primary-500, text-neutral-700, border-danger-300, etc.
|
|
106
|
+
--------------------------------------------------------------- */
|
|
107
|
+
@theme inline {
|
|
108
|
+
/* Neutral */
|
|
109
|
+
--color-neutral-50: var(--color-neutral-50);
|
|
110
|
+
--color-neutral-100: var(--color-neutral-100);
|
|
111
|
+
--color-neutral-200: var(--color-neutral-200);
|
|
112
|
+
--color-neutral-300: var(--color-neutral-300);
|
|
113
|
+
--color-neutral-400: var(--color-neutral-400);
|
|
114
|
+
--color-neutral-500: var(--color-neutral-500);
|
|
115
|
+
--color-neutral-600: var(--color-neutral-600);
|
|
116
|
+
--color-neutral-700: var(--color-neutral-700);
|
|
117
|
+
--color-neutral-800: var(--color-neutral-800);
|
|
118
|
+
--color-neutral-900: var(--color-neutral-900);
|
|
119
|
+
--color-neutral-950: var(--color-neutral-950);
|
|
120
|
+
|
|
121
|
+
/* Primary */
|
|
122
|
+
--color-primary-50: var(--color-primary-50);
|
|
123
|
+
--color-primary-100: var(--color-primary-100);
|
|
124
|
+
--color-primary-200: var(--color-primary-200);
|
|
125
|
+
--color-primary-300: var(--color-primary-300);
|
|
126
|
+
--color-primary-400: var(--color-primary-400);
|
|
127
|
+
--color-primary-500: var(--color-primary-500);
|
|
128
|
+
--color-primary-600: var(--color-primary-600);
|
|
129
|
+
--color-primary-700: var(--color-primary-700);
|
|
130
|
+
--color-primary-800: var(--color-primary-800);
|
|
131
|
+
--color-primary-900: var(--color-primary-900);
|
|
132
|
+
--color-primary-950: var(--color-primary-950);
|
|
133
|
+
|
|
134
|
+
/* Secondary */
|
|
135
|
+
--color-secondary-50: var(--color-secondary-50);
|
|
136
|
+
--color-secondary-100: var(--color-secondary-100);
|
|
137
|
+
--color-secondary-200: var(--color-secondary-200);
|
|
138
|
+
--color-secondary-300: var(--color-secondary-300);
|
|
139
|
+
--color-secondary-400: var(--color-secondary-400);
|
|
140
|
+
--color-secondary-500: var(--color-secondary-500);
|
|
141
|
+
--color-secondary-600: var(--color-secondary-600);
|
|
142
|
+
--color-secondary-700: var(--color-secondary-700);
|
|
143
|
+
--color-secondary-800: var(--color-secondary-800);
|
|
144
|
+
--color-secondary-900: var(--color-secondary-900);
|
|
145
|
+
--color-secondary-950: var(--color-secondary-950);
|
|
146
|
+
|
|
147
|
+
/* Accent */
|
|
148
|
+
--color-accent-50: var(--color-accent-50);
|
|
149
|
+
--color-accent-100: var(--color-accent-100);
|
|
150
|
+
--color-accent-200: var(--color-accent-200);
|
|
151
|
+
--color-accent-300: var(--color-accent-300);
|
|
152
|
+
--color-accent-400: var(--color-accent-400);
|
|
153
|
+
--color-accent-500: var(--color-accent-500);
|
|
154
|
+
--color-accent-600: var(--color-accent-600);
|
|
155
|
+
--color-accent-700: var(--color-accent-700);
|
|
156
|
+
--color-accent-800: var(--color-accent-800);
|
|
157
|
+
--color-accent-900: var(--color-accent-900);
|
|
158
|
+
--color-accent-950: var(--color-accent-950);
|
|
159
|
+
|
|
160
|
+
/* Success */
|
|
161
|
+
--color-success-50: var(--color-success-50);
|
|
162
|
+
--color-success-100: var(--color-success-100);
|
|
163
|
+
--color-success-200: var(--color-success-200);
|
|
164
|
+
--color-success-300: var(--color-success-300);
|
|
165
|
+
--color-success-400: var(--color-success-400);
|
|
166
|
+
--color-success-500: var(--color-success-500);
|
|
167
|
+
--color-success-600: var(--color-success-600);
|
|
168
|
+
--color-success-700: var(--color-success-700);
|
|
169
|
+
--color-success-800: var(--color-success-800);
|
|
170
|
+
--color-success-900: var(--color-success-900);
|
|
171
|
+
--color-success-950: var(--color-success-950);
|
|
172
|
+
|
|
173
|
+
/* Warning */
|
|
174
|
+
--color-warning-50: var(--color-warning-50);
|
|
175
|
+
--color-warning-100: var(--color-warning-100);
|
|
176
|
+
--color-warning-200: var(--color-warning-200);
|
|
177
|
+
--color-warning-300: var(--color-warning-300);
|
|
178
|
+
--color-warning-400: var(--color-warning-400);
|
|
179
|
+
--color-warning-500: var(--color-warning-500);
|
|
180
|
+
--color-warning-600: var(--color-warning-600);
|
|
181
|
+
--color-warning-700: var(--color-warning-700);
|
|
182
|
+
--color-warning-800: var(--color-warning-800);
|
|
183
|
+
--color-warning-900: var(--color-warning-900);
|
|
184
|
+
--color-warning-950: var(--color-warning-950);
|
|
185
|
+
|
|
186
|
+
/* Danger */
|
|
187
|
+
--color-danger-50: var(--color-danger-50);
|
|
188
|
+
--color-danger-100: var(--color-danger-100);
|
|
189
|
+
--color-danger-200: var(--color-danger-200);
|
|
190
|
+
--color-danger-300: var(--color-danger-300);
|
|
191
|
+
--color-danger-400: var(--color-danger-400);
|
|
192
|
+
--color-danger-500: var(--color-danger-500);
|
|
193
|
+
--color-danger-600: var(--color-danger-600);
|
|
194
|
+
--color-danger-700: var(--color-danger-700);
|
|
195
|
+
--color-danger-800: var(--color-danger-800);
|
|
196
|
+
--color-danger-900: var(--color-danger-900);
|
|
197
|
+
--color-danger-950: var(--color-danger-950);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* ---------------------------------------------------------------
|
|
201
|
+
Non-color tokens — these don't need multi-tenant override,
|
|
202
|
+
so @theme is fine (Tailwind keeps them if used in utilities).
|
|
203
|
+
--------------------------------------------------------------- */
|
|
204
|
+
@theme inline {
|
|
205
|
+
/* Typography — values managed by Astro's fonts config (astro.config.mjs).
|
|
206
|
+
@theme inline bridges them as Tailwind utilities (font-sans, etc.)
|
|
207
|
+
without overriding the Astro-generated @font-face values. */
|
|
208
|
+
--font-sans: var(--font-sans);
|
|
209
|
+
--font-serif: var(--font-serif);
|
|
210
|
+
--font-mono: var(--font-mono);
|
|
211
|
+
--font-heading-alt: var(--font-heading-alt);
|
|
212
|
+
--font-body-alt: var(--font-body-alt);
|
|
213
|
+
--font-rounded: var(--font-rounded);
|
|
214
|
+
--font-tech: var(--font-tech);
|
|
215
|
+
--font-tech-body: var(--font-tech-body);
|
|
216
|
+
--font-inter: var(--font-inter);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@theme {
|
|
220
|
+
|
|
221
|
+
/* Border radius */
|
|
222
|
+
--radius-xs: 0.25rem;
|
|
223
|
+
--radius-sm: 0.375rem;
|
|
224
|
+
--radius-md: 0.5rem;
|
|
225
|
+
--radius-lg: 0.75rem;
|
|
226
|
+
--radius-xl: 1rem;
|
|
227
|
+
--radius-2xl: 1.5rem;
|
|
228
|
+
--radius-full: 9999px;
|
|
229
|
+
|
|
230
|
+
/* Shadows */
|
|
231
|
+
--shadow-xs: 0 1px 2px 0 oklch(0 0 0 / 0.05);
|
|
232
|
+
--shadow-sm: 0 1px 3px 0 oklch(0 0 0 / 0.1), 0 1px 2px -1px oklch(0 0 0 / 0.1);
|
|
233
|
+
--shadow-md: 0 4px 6px -1px oklch(0 0 0 / 0.1), 0 2px 4px -2px oklch(0 0 0 / 0.1);
|
|
234
|
+
--shadow-lg: 0 10px 15px -3px oklch(0 0 0 / 0.1), 0 4px 6px -4px oklch(0 0 0 / 0.1);
|
|
235
|
+
--shadow-xl: 0 20px 25px -5px oklch(0 0 0 / 0.1), 0 8px 10px -6px oklch(0 0 0 / 0.1);
|
|
236
|
+
--shadow-2xl: 0 25px 50px -12px oklch(0 0 0 / 0.25);
|
|
237
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
const linkSchema = z.object({
|
|
4
|
+
label: z.string(),
|
|
5
|
+
href: z.string(),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const actionSchema = linkSchema.extend({
|
|
9
|
+
variant: z.enum(['primary', 'secondary', 'ghost']).default('primary'),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const footerColumnSchema = z.object({
|
|
13
|
+
title: z.string(),
|
|
14
|
+
links: z.array(linkSchema),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const verificationSchema = z.object({
|
|
18
|
+
google: z.string().optional(),
|
|
19
|
+
bing: z.string().optional(),
|
|
20
|
+
yandex: z.string().optional(),
|
|
21
|
+
pinterest: z.string().optional(),
|
|
22
|
+
}).default({});
|
|
23
|
+
|
|
24
|
+
const defaultRobotsSchema = z.object({
|
|
25
|
+
maxSnippet: z.number().default(-1),
|
|
26
|
+
maxImagePreview: z.enum(['none', 'standard', 'large']).default('large'),
|
|
27
|
+
maxVideoPreview: z.number().default(-1),
|
|
28
|
+
}).default({});
|
|
29
|
+
|
|
30
|
+
const addressSchema = z.object({
|
|
31
|
+
street: z.string().optional(),
|
|
32
|
+
city: z.string().optional(),
|
|
33
|
+
region: z.string().optional(),
|
|
34
|
+
postalCode: z.string().optional(),
|
|
35
|
+
country: z.string().optional(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const contactPointSchema = z.object({
|
|
39
|
+
telephone: z.string().optional(),
|
|
40
|
+
contactType: z.string().optional(),
|
|
41
|
+
email: z.string().optional(),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const siteConfigSchema = z.object({
|
|
45
|
+
site: z.object({
|
|
46
|
+
name: z.string(),
|
|
47
|
+
description: z.string().default(''),
|
|
48
|
+
url: z.string().url().optional(),
|
|
49
|
+
defaultLanguage: z.string().default('en'),
|
|
50
|
+
logo: z.string().optional(),
|
|
51
|
+
}),
|
|
52
|
+
|
|
53
|
+
metadata: z.object({
|
|
54
|
+
ogImage: z.string().optional(),
|
|
55
|
+
twitterHandle: z.string().optional(),
|
|
56
|
+
}).default({}),
|
|
57
|
+
|
|
58
|
+
seo: z.object({
|
|
59
|
+
verification: verificationSchema,
|
|
60
|
+
defaultRobots: defaultRobotsSchema,
|
|
61
|
+
defaultOgType: z.enum(['website', 'article', 'product', 'profile']).default('website'),
|
|
62
|
+
defaultTwitterCard: z.enum(['summary', 'summary_large_image', 'player', 'app']).default('summary_large_image'),
|
|
63
|
+
allowAICrawlers: z.boolean().default(true),
|
|
64
|
+
preconnect: z.array(z.string()).default([]),
|
|
65
|
+
}).default({}),
|
|
66
|
+
|
|
67
|
+
organization: z.object({
|
|
68
|
+
type: z.enum(['Organization', 'LocalBusiness', 'Corporation']).default('Organization'),
|
|
69
|
+
name: z.string().optional(),
|
|
70
|
+
legalName: z.string().optional(),
|
|
71
|
+
logo: z.string().optional(),
|
|
72
|
+
url: z.string().optional(),
|
|
73
|
+
description: z.string().optional(),
|
|
74
|
+
foundingDate: z.string().optional(),
|
|
75
|
+
socialProfiles: z.array(z.string()).default([]),
|
|
76
|
+
address: addressSchema.optional(),
|
|
77
|
+
contactPoint: contactPointSchema.optional(),
|
|
78
|
+
}).default({}),
|
|
79
|
+
|
|
80
|
+
header: z.object({
|
|
81
|
+
logo: z.string().optional(),
|
|
82
|
+
links: z.array(linkSchema).default([]),
|
|
83
|
+
actions: z.array(actionSchema).default([]),
|
|
84
|
+
}).default({}),
|
|
85
|
+
|
|
86
|
+
footer: z.object({
|
|
87
|
+
columns: z.array(footerColumnSchema).default([]),
|
|
88
|
+
copyright: z.string().default(''),
|
|
89
|
+
socialLinks: z.array(linkSchema).default([]),
|
|
90
|
+
}).default({}),
|
|
91
|
+
|
|
92
|
+
theme: z.object({
|
|
93
|
+
name: z.string().default(''),
|
|
94
|
+
darkMode: z.boolean().default(true),
|
|
95
|
+
}).default({}),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
export type SiteConfig = z.infer<typeof siteConfigSchema>;
|
|
99
|
+
|
|
100
|
+
export function defineConfig(config: z.input<typeof siteConfigSchema>): SiteConfig {
|
|
101
|
+
return siteConfigSchema.parse(config);
|
|
102
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { getCollection } from 'astro:content';
|
|
2
|
+
|
|
3
|
+
// These are injected at build time via the parche:config/i18n virtual module.
|
|
4
|
+
// Importing here would create a circular dependency, so the functions accept
|
|
5
|
+
// defaultLocale as a parameter and the route/middleware passes it in.
|
|
6
|
+
|
|
7
|
+
export interface SlugMapEntry {
|
|
8
|
+
locale: string;
|
|
9
|
+
pageKey: string;
|
|
10
|
+
slug: string;
|
|
11
|
+
data: any;
|
|
12
|
+
entryId: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function buildSlugMap(): Promise<SlugMapEntry[]> {
|
|
16
|
+
const allPages = await getCollection('pages');
|
|
17
|
+
const map: SlugMapEntry[] = [];
|
|
18
|
+
|
|
19
|
+
for (const entry of allPages) {
|
|
20
|
+
const parts = entry.id.split('/');
|
|
21
|
+
const locale = parts[0];
|
|
22
|
+
const pageKey = parts.slice(1).join('/');
|
|
23
|
+
const slug = entry.data.urlSlug ?? pageKey;
|
|
24
|
+
map.push({ locale, pageKey, slug, data: entry.data, entryId: entry.id });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return map;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function resolvePageFromSlug(
|
|
31
|
+
urlSlug: string | undefined,
|
|
32
|
+
defaultLocale: string,
|
|
33
|
+
): Promise<{ pageData: any; locale: string; pageKey: string; entryId: string } | undefined> {
|
|
34
|
+
const slugMap = await buildSlugMap();
|
|
35
|
+
|
|
36
|
+
for (const entry of slugMap) {
|
|
37
|
+
let expectedSlug: string | undefined;
|
|
38
|
+
if (entry.pageKey === 'home') {
|
|
39
|
+
expectedSlug = entry.locale === defaultLocale ? undefined : entry.locale;
|
|
40
|
+
} else if (entry.locale === defaultLocale) {
|
|
41
|
+
expectedSlug = entry.slug;
|
|
42
|
+
} else {
|
|
43
|
+
expectedSlug = `${entry.locale}/${entry.slug}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if ((expectedSlug ?? '') === (urlSlug ?? '')) {
|
|
47
|
+
return { pageData: entry.data, locale: entry.locale, pageKey: entry.pageKey, entryId: entry.entryId };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getAlternateUrls(
|
|
55
|
+
pageKey: string,
|
|
56
|
+
slugMap: SlugMapEntry[],
|
|
57
|
+
defaultLocale: string,
|
|
58
|
+
site?: URL,
|
|
59
|
+
): Array<{ locale: string; href: string; path: string }> {
|
|
60
|
+
const alternates: Array<{ locale: string; href: string; path: string }> = [];
|
|
61
|
+
|
|
62
|
+
for (const entry of slugMap) {
|
|
63
|
+
if (entry.pageKey !== pageKey) continue;
|
|
64
|
+
|
|
65
|
+
let path: string;
|
|
66
|
+
if (entry.pageKey === 'home') {
|
|
67
|
+
path = entry.locale === defaultLocale ? '/' : `/${entry.locale}`;
|
|
68
|
+
} else if (entry.locale === defaultLocale) {
|
|
69
|
+
path = `/${entry.slug}`;
|
|
70
|
+
} else {
|
|
71
|
+
path = `/${entry.locale}/${entry.slug}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const href = site ? new URL(path, site).href : path;
|
|
75
|
+
alternates.push({ locale: entry.locale, href, path });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return alternates;
|
|
79
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getCollection } from 'astro:content';
|
|
2
|
+
import type { SectionEntry } from '../content/schemas.js';
|
|
3
|
+
|
|
4
|
+
/** Hardcoded base layout: just a Main area (no header/footer) */
|
|
5
|
+
const BASE_LAYOUT_SECTIONS: SectionEntry[] = [{ widget: 'layout/Main' }];
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolve a layout by name and locale from the layouts content collection.
|
|
9
|
+
*
|
|
10
|
+
* Fallback chain:
|
|
11
|
+
* 1. {locale}/{name}
|
|
12
|
+
* 2. {defaultLocale}/{name}
|
|
13
|
+
* 3. {locale}/default
|
|
14
|
+
* 4. {defaultLocale}/default
|
|
15
|
+
* 5. Hardcoded base: [{ widget: 'layout/Main' }]
|
|
16
|
+
*/
|
|
17
|
+
export async function resolveLayout(
|
|
18
|
+
name: string,
|
|
19
|
+
locale: string,
|
|
20
|
+
defaultLocale: string,
|
|
21
|
+
): Promise<SectionEntry[]> {
|
|
22
|
+
let allLayouts;
|
|
23
|
+
try {
|
|
24
|
+
allLayouts = await getCollection('layouts');
|
|
25
|
+
} catch {
|
|
26
|
+
return BASE_LAYOUT_SECTIONS;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!allLayouts || allLayouts.length === 0) {
|
|
30
|
+
return BASE_LAYOUT_SECTIONS;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const find = (id: string) => allLayouts.find((entry) => entry.id === id);
|
|
34
|
+
|
|
35
|
+
const entry =
|
|
36
|
+
find(`${locale}/${name}`) ??
|
|
37
|
+
find(`${defaultLocale}/${name}`) ??
|
|
38
|
+
find(`${locale}/default`) ??
|
|
39
|
+
find(`${defaultLocale}/default`);
|
|
40
|
+
|
|
41
|
+
if (!entry) {
|
|
42
|
+
return BASE_LAYOUT_SECTIONS;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return entry.data.sections;
|
|
46
|
+
}
|