@hkdigital/lib-core 0.5.93 → 0.5.94
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/dist/config/generators/imagetools.d.ts +14 -0
- package/dist/config/generators/imagetools.js +55 -0
- package/dist/config/imagetools.d.ts +12 -0
- package/dist/meta/README.md +92 -0
- package/dist/meta/components/Favicons.svelte +30 -0
- package/dist/meta/components/Favicons.svelte.d.ts +103 -0
- package/dist/meta/components/PWA.svelte +51 -0
- package/dist/meta/components/PWA.svelte.d.ts +103 -0
- package/dist/meta/components/SEO.svelte +146 -0
- package/dist/meta/components/SEO.svelte.d.ts +108 -0
- package/dist/meta/components.d.ts +3 -0
- package/dist/meta/components.js +3 -0
- package/dist/meta/config.typedef.d.ts +98 -0
- package/dist/meta/config.typedef.js +44 -0
- package/dist/meta/typedef.d.ts +3 -0
- package/dist/meta/typedef.js +14 -0
- package/dist/meta/utils/lang.d.ts +29 -0
- package/dist/meta/utils/lang.js +84 -0
- package/dist/meta/utils/robots.d.ts +1 -0
- package/dist/meta/utils/robots.js +1 -0
- package/dist/meta/utils/sitemap.d.ts +1 -0
- package/dist/meta/utils/sitemap.js +1 -0
- package/dist/meta/utils.d.ts +3 -0
- package/dist/meta/utils.js +11 -0
- package/package.json +1 -1
- package/dist/meta/robots.d.ts +0 -1
- package/dist/meta/robots.js +0 -5
- package/dist/meta/sitemap.d.ts +0 -1
- package/dist/meta/sitemap.js +0 -5
- /package/dist/meta/{robots/index.d.ts → utils/robots/robots.d.ts} +0 -0
- /package/dist/meta/{robots/index.js → utils/robots/robots.js} +0 -0
- /package/dist/meta/{robots → utils/robots}/typedef.d.ts +0 -0
- /package/dist/meta/{robots → utils/robots}/typedef.js +0 -0
- /package/dist/meta/{sitemap/index.d.ts → utils/sitemap/sitemap.d.ts} +0 -0
- /package/dist/meta/{sitemap/index.js → utils/sitemap/sitemap.js} +0 -0
- /package/dist/meta/{sitemap → utils/sitemap}/typedef.d.ts +0 -0
- /package/dist/meta/{sitemap → utils/sitemap}/typedef.js +0 -0
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
* @param {number[]} [options.faviconSizes=FAVICON_SIZES]
|
|
9
9
|
* @param {number[]} [options.appleTouchSizes=APPLE_TOUCH_SIZES]
|
|
10
10
|
*
|
|
11
|
+
* @param {{width: number, height: number}} [options.seoLandscapeSize]
|
|
12
|
+
* SEO landscape image size
|
|
13
|
+
*
|
|
14
|
+
* @param {{width: number, height: number}} [options.seoSquareSize]
|
|
15
|
+
* SEO square image size
|
|
16
|
+
*
|
|
11
17
|
* @returns {(
|
|
12
18
|
* entries: [string, string[]][]
|
|
13
19
|
* ) => (Record<string, string | string[]>[])}
|
|
@@ -17,6 +23,14 @@ export function generateResponseConfigs(options?: {
|
|
|
17
23
|
thumbnailWidth?: number | undefined;
|
|
18
24
|
faviconSizes?: number[] | undefined;
|
|
19
25
|
appleTouchSizes?: number[] | undefined;
|
|
26
|
+
seoLandscapeSize?: {
|
|
27
|
+
width: number;
|
|
28
|
+
height: number;
|
|
29
|
+
} | undefined;
|
|
30
|
+
seoSquareSize?: {
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
} | undefined;
|
|
20
34
|
}): (entries: [string, string[]][]) => (Record<string, string | string[]>[]);
|
|
21
35
|
/**
|
|
22
36
|
* Configures and returns a function that can be used as
|
|
@@ -21,6 +21,9 @@ const APPLE_TOUCH_SIZES = [
|
|
|
21
21
|
180 // iPhone retina, iOS home screen
|
|
22
22
|
];
|
|
23
23
|
|
|
24
|
+
const SEO_LANDSCAPE_SIZE = { width: 1200, height: 630 };
|
|
25
|
+
const SEO_SQUARE_SIZE = { width: 1200, height: 1200 };
|
|
26
|
+
|
|
24
27
|
const DEFAULT_PRESETS = {
|
|
25
28
|
default: {
|
|
26
29
|
format: 'avif',
|
|
@@ -70,6 +73,12 @@ const DEFAULT_PRESETS = {
|
|
|
70
73
|
* @param {number[]} [options.faviconSizes=FAVICON_SIZES]
|
|
71
74
|
* @param {number[]} [options.appleTouchSizes=APPLE_TOUCH_SIZES]
|
|
72
75
|
*
|
|
76
|
+
* @param {{width: number, height: number}} [options.seoLandscapeSize]
|
|
77
|
+
* SEO landscape image size
|
|
78
|
+
*
|
|
79
|
+
* @param {{width: number, height: number}} [options.seoSquareSize]
|
|
80
|
+
* SEO square image size
|
|
81
|
+
*
|
|
73
82
|
* @returns {(
|
|
74
83
|
* entries: [string, string[]][]
|
|
75
84
|
* ) => (Record<string, string | string[]>[])}
|
|
@@ -115,15 +124,25 @@ export function generateResponseConfigs(options) {
|
|
|
115
124
|
|
|
116
125
|
// @ts-ignore
|
|
117
126
|
const isAppleTouchIcon = !!entries.find(([key]) => key === 'apple-touch-icons');
|
|
127
|
+
|
|
128
|
+
// @ts-ignore
|
|
129
|
+
const isSeoLandscape = !!entries.find(([key]) => key === 'seo-landscape');
|
|
130
|
+
|
|
131
|
+
// @ts-ignore
|
|
132
|
+
const isSeoSquare = !!entries.find(([key]) => key === 'seo-square');
|
|
118
133
|
// console.log('responsiveConfig found:', !!responsiveConfig);
|
|
119
134
|
|
|
120
135
|
const widths = options?.widths ?? DEFAULT_WIDTHS;
|
|
121
136
|
const faviconSizes = options?.faviconSizes ?? FAVICON_SIZES;
|
|
122
137
|
const appleTouchSizes = options?.appleTouchSizes ?? APPLE_TOUCH_SIZES;
|
|
138
|
+
const seoLandscapeSize = options?.seoLandscapeSize ?? SEO_LANDSCAPE_SIZE;
|
|
139
|
+
const seoSquareSize = options?.seoSquareSize ?? SEO_SQUARE_SIZE;
|
|
123
140
|
|
|
124
141
|
delete configPairs.responsive;
|
|
125
142
|
delete configPairs.favicons;
|
|
126
143
|
delete configPairs['apple-touch-icons'];
|
|
144
|
+
delete configPairs['seo-landscape'];
|
|
145
|
+
delete configPairs['seo-square'];
|
|
127
146
|
|
|
128
147
|
// Always include the main image(s) and a thumbnail version
|
|
129
148
|
const thumbnailConfig = {
|
|
@@ -163,6 +182,30 @@ export function generateResponseConfigs(options) {
|
|
|
163
182
|
return appleTouchConfigs;
|
|
164
183
|
}
|
|
165
184
|
|
|
185
|
+
// Handle seo-landscape directive - generate landscape SEO image
|
|
186
|
+
|
|
187
|
+
if (isSeoLandscape) {
|
|
188
|
+
return [{
|
|
189
|
+
...configPairs,
|
|
190
|
+
w: String(seoLandscapeSize.width),
|
|
191
|
+
h: String(seoLandscapeSize.height),
|
|
192
|
+
format: 'jpg',
|
|
193
|
+
quality: '95'
|
|
194
|
+
}];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Handle seo-square directive - generate square SEO image
|
|
198
|
+
|
|
199
|
+
if (isSeoSquare) {
|
|
200
|
+
return [{
|
|
201
|
+
...configPairs,
|
|
202
|
+
w: String(seoSquareSize.width),
|
|
203
|
+
h: String(seoSquareSize.height),
|
|
204
|
+
format: 'jpg',
|
|
205
|
+
quality: '95'
|
|
206
|
+
}];
|
|
207
|
+
}
|
|
208
|
+
|
|
166
209
|
if (!responsiveConfig) {
|
|
167
210
|
// Directive 'responsive' was not set => return original + thumbnail
|
|
168
211
|
const originalConfig = configPairs; // No 'w' means original dimensions
|
|
@@ -227,6 +270,18 @@ export function generateDefaultDirectives(options) {
|
|
|
227
270
|
params.set('as', 'metadata');
|
|
228
271
|
}
|
|
229
272
|
|
|
273
|
+
// > Return picture (URL) if directive 'seo-landscape' is set
|
|
274
|
+
|
|
275
|
+
if (params.has('seo-landscape')) {
|
|
276
|
+
params.set('as', 'picture');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// > Return picture (URL) if directive 'seo-square' is set
|
|
280
|
+
|
|
281
|
+
if (params.has('seo-square')) {
|
|
282
|
+
params.set('as', 'picture');
|
|
283
|
+
}
|
|
284
|
+
|
|
230
285
|
// > Process presets
|
|
231
286
|
|
|
232
287
|
if (presetName) {
|
|
@@ -84,3 +84,15 @@ declare module '*?apple-touch-icons' {
|
|
|
84
84
|
const out: ImageSource;
|
|
85
85
|
export default out;
|
|
86
86
|
}
|
|
87
|
+
|
|
88
|
+
// Generate SEO landscape image (1200x630)
|
|
89
|
+
declare module '*?seo-landscape' {
|
|
90
|
+
const out: string;
|
|
91
|
+
export default out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Generate SEO square image (1200x1200)
|
|
95
|
+
declare module '*?seo-square' {
|
|
96
|
+
const out: string;
|
|
97
|
+
export default out;
|
|
98
|
+
}
|
package/dist/meta/README.md
CHANGED
|
@@ -62,6 +62,12 @@ export const GET = async ({ url }) => {
|
|
|
62
62
|
* Supports wildcards (e.g., '*.example.com')
|
|
63
63
|
* @property {string[]} [disallowedPaths]
|
|
64
64
|
* Paths to block from indexing (e.g., '/admin', '/api/*')
|
|
65
|
+
* @property {boolean} [allowAiTraining=true]
|
|
66
|
+
* Allow AI training bots to crawl content for model training.
|
|
67
|
+
* Set to false to block bots like GPTBot, Google-Extended, CCBot, etc.
|
|
68
|
+
* @property {boolean} [allowAiReading=true]
|
|
69
|
+
* Allow AI assistants/chatbots to read content for user responses.
|
|
70
|
+
* Set to false to block bots like ChatGPT-User, Claude-Web, etc.
|
|
65
71
|
*/
|
|
66
72
|
```
|
|
67
73
|
|
|
@@ -108,6 +114,54 @@ const config = {
|
|
|
108
114
|
// Sitemap: https://example.com/sitemap.xml
|
|
109
115
|
```
|
|
110
116
|
|
|
117
|
+
**AI bot control:**
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
// Block all AI bots from training on your content
|
|
121
|
+
const config = {
|
|
122
|
+
allowedHosts: '*',
|
|
123
|
+
allowAiTraining: false
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Generates additional blocks:
|
|
127
|
+
// User-agent: GPTBot
|
|
128
|
+
// Disallow: /
|
|
129
|
+
//
|
|
130
|
+
// User-agent: Google-Extended
|
|
131
|
+
// Disallow: /
|
|
132
|
+
// ... (and other AI training bots)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**AI training vs AI reading:**
|
|
136
|
+
|
|
137
|
+
```javascript
|
|
138
|
+
// Block training but allow reading (ChatGPT can browse, but not train)
|
|
139
|
+
const config = {
|
|
140
|
+
allowAiTraining: false, // Blocks GPTBot, Google-Extended, CCBot
|
|
141
|
+
allowAiReading: true // Allows ChatGPT-User, Claude-Web
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Block both training and reading
|
|
145
|
+
const config = {
|
|
146
|
+
allowAiTraining: false,
|
|
147
|
+
allowAiReading: false
|
|
148
|
+
};
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Blocked AI training bots:**
|
|
152
|
+
- `GPTBot` - OpenAI training
|
|
153
|
+
- `Google-Extended` - Google AI training
|
|
154
|
+
- `CCBot` - Common Crawl
|
|
155
|
+
- `anthropic-ai` - Anthropic training
|
|
156
|
+
- `FacebookBot` - Meta AI training
|
|
157
|
+
- `PerplexityBot` - Perplexity AI training
|
|
158
|
+
- And others (see `src/lib/meta/robots/index.js` for full list)
|
|
159
|
+
|
|
160
|
+
**Blocked AI reading bots:**
|
|
161
|
+
- `ChatGPT-User` - ChatGPT browsing
|
|
162
|
+
- `Claude-Web` - Claude browsing
|
|
163
|
+
- `cohere-ai` - Cohere browsing
|
|
164
|
+
|
|
111
165
|
**Sitemap reference:**
|
|
112
166
|
|
|
113
167
|
Sitemap reference is always included for allowed hosts:
|
|
@@ -277,6 +331,44 @@ const robotsConfig = {
|
|
|
277
331
|
// All hosts → Allow + Sitemap
|
|
278
332
|
```
|
|
279
333
|
|
|
334
|
+
### Block AI training but allow search engines
|
|
335
|
+
|
|
336
|
+
```javascript
|
|
337
|
+
// Protect premium content from AI training while allowing SEO
|
|
338
|
+
const robotsConfig = {
|
|
339
|
+
allowedHosts: ['mysite.com', 'www.mysite.com'],
|
|
340
|
+
disallowedPaths: ['/admin'],
|
|
341
|
+
allowAiTraining: false, // Block GPTBot, Google-Extended, etc.
|
|
342
|
+
allowAiReading: true // Allow ChatGPT browsing (user queries)
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// Generates:
|
|
346
|
+
// User-agent: *
|
|
347
|
+
// Allow: /
|
|
348
|
+
// Disallow: /admin
|
|
349
|
+
// Sitemap: https://mysite.com/sitemap.xml
|
|
350
|
+
//
|
|
351
|
+
// User-agent: GPTBot
|
|
352
|
+
// Disallow: /
|
|
353
|
+
//
|
|
354
|
+
// User-agent: Google-Extended
|
|
355
|
+
// Disallow: /
|
|
356
|
+
// ... (other AI training bots)
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Block all AI bots completely
|
|
360
|
+
|
|
361
|
+
```javascript
|
|
362
|
+
// Block both AI training and AI reading/browsing
|
|
363
|
+
const robotsConfig = {
|
|
364
|
+
allowedHosts: ['mysite.com'],
|
|
365
|
+
allowAiTraining: false,
|
|
366
|
+
allowAiReading: false
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// Blocks: GPTBot, Google-Extended, CCBot, ChatGPT-User, Claude-Web, etc.
|
|
370
|
+
```
|
|
371
|
+
|
|
280
372
|
### Complex sitemap configuration
|
|
281
373
|
|
|
282
374
|
```javascript
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* Favicons component
|
|
4
|
+
*
|
|
5
|
+
* Generates favicon and apple-touch-icon links for browsers and PWAs.
|
|
6
|
+
*
|
|
7
|
+
* @typedef {import('../typedef.js').MetaConfig} MetaConfig
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** @type {{ config: MetaConfig }} */
|
|
11
|
+
let { config } = $props();
|
|
12
|
+
|
|
13
|
+
const { faviconImages, appleTouchIcons } = config;
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<svelte:head>
|
|
17
|
+
<!-- Basic favicon configuration -->
|
|
18
|
+
{#each faviconImages as img}
|
|
19
|
+
<link
|
|
20
|
+
rel="icon"
|
|
21
|
+
type="image/png"
|
|
22
|
+
sizes="{img.width}x{img.width}"
|
|
23
|
+
href={img.src}
|
|
24
|
+
/>
|
|
25
|
+
{/each}
|
|
26
|
+
|
|
27
|
+
{#each appleTouchIcons as img}
|
|
28
|
+
<link rel="apple-touch-icon" sizes="{img.width}x{img.width}" href={img.src} />
|
|
29
|
+
{/each}
|
|
30
|
+
</svelte:head>
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export default Favicons;
|
|
2
|
+
type Favicons = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
declare const Favicons: import("svelte").Component<{
|
|
7
|
+
config: import("../config.typedef.js").MetaConfig;
|
|
8
|
+
}, {}, "">;
|
|
9
|
+
type $$ComponentProps = {
|
|
10
|
+
config: {
|
|
11
|
+
/**
|
|
12
|
+
* - Full app name
|
|
13
|
+
*/
|
|
14
|
+
name: string;
|
|
15
|
+
/**
|
|
16
|
+
* - Short app name (max 12 characters)
|
|
17
|
+
*/
|
|
18
|
+
shortName: string;
|
|
19
|
+
/**
|
|
20
|
+
* - App description for search engines
|
|
21
|
+
*
|
|
22
|
+
* Language and locale
|
|
23
|
+
*/
|
|
24
|
+
description: string;
|
|
25
|
+
/**
|
|
26
|
+
* Language configurations
|
|
27
|
+
*/
|
|
28
|
+
languages: Record<string, {
|
|
29
|
+
lang: string;
|
|
30
|
+
locale: string;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* - Default language code
|
|
34
|
+
*/
|
|
35
|
+
defaultLanguage: string;
|
|
36
|
+
/**
|
|
37
|
+
* - Default locale
|
|
38
|
+
*
|
|
39
|
+
* PWA theme and colors
|
|
40
|
+
*/
|
|
41
|
+
defaultLocale: string;
|
|
42
|
+
/**
|
|
43
|
+
* - Theme color
|
|
44
|
+
*/
|
|
45
|
+
backgroundAndThemeColor: string;
|
|
46
|
+
/**
|
|
47
|
+
* - Theme color for browser UI
|
|
48
|
+
*/
|
|
49
|
+
themeColor: string;
|
|
50
|
+
/**
|
|
51
|
+
* - Background color
|
|
52
|
+
*/
|
|
53
|
+
backgroundColor: string;
|
|
54
|
+
/**
|
|
55
|
+
* - iOS status bar style
|
|
56
|
+
*/
|
|
57
|
+
statusBarStyle: string;
|
|
58
|
+
/**
|
|
59
|
+
* - Screen orientation
|
|
60
|
+
*/
|
|
61
|
+
orientation: string;
|
|
62
|
+
/**
|
|
63
|
+
* - Disable pinch-to-zoom
|
|
64
|
+
*
|
|
65
|
+
* SEO images
|
|
66
|
+
*/
|
|
67
|
+
disablePageZoom: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* - Landscape SEO image URL (1200×630)
|
|
70
|
+
*/
|
|
71
|
+
SeoImageLandscape?: string | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* - Square SEO image URL (1200×1200)
|
|
74
|
+
*
|
|
75
|
+
* Favicon images (processed by imagetools)
|
|
76
|
+
*/
|
|
77
|
+
SeoImageSquare?: string | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* Processed favicon images
|
|
80
|
+
*/
|
|
81
|
+
faviconImages: Array<{
|
|
82
|
+
src: string;
|
|
83
|
+
width: number;
|
|
84
|
+
}>;
|
|
85
|
+
/**
|
|
86
|
+
* Processed apple-touch-icon images
|
|
87
|
+
*
|
|
88
|
+
* Site configuration
|
|
89
|
+
*/
|
|
90
|
+
appleTouchIcons: Array<{
|
|
91
|
+
src: string;
|
|
92
|
+
width: number;
|
|
93
|
+
}>;
|
|
94
|
+
/**
|
|
95
|
+
* Routes for sitemap.xml
|
|
96
|
+
*/
|
|
97
|
+
siteRoutes: import("../typedef.js").SitemapRoute[];
|
|
98
|
+
/**
|
|
99
|
+
* Robots.txt configuration
|
|
100
|
+
*/
|
|
101
|
+
robotsConfig: import("../typedef.js").RobotsConfig;
|
|
102
|
+
};
|
|
103
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* PWA component
|
|
6
|
+
*
|
|
7
|
+
* Generates Progressive Web App meta tags and viewport configuration.
|
|
8
|
+
*
|
|
9
|
+
* @typedef {import('../typedef.js').MetaConfig} MetaConfig
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** @type {{ config: MetaConfig }} */
|
|
13
|
+
let { config } = $props();
|
|
14
|
+
|
|
15
|
+
const { themeColor, statusBarStyle, name, shortName, disablePageZoom } =
|
|
16
|
+
config;
|
|
17
|
+
|
|
18
|
+
let shouldSetTitle = $state(false);
|
|
19
|
+
|
|
20
|
+
onMount(() => {
|
|
21
|
+
// Check if title element exists and has content
|
|
22
|
+
const titleElement = document.querySelector('title');
|
|
23
|
+
const hasTitle = titleElement && titleElement.textContent.trim() !== '';
|
|
24
|
+
|
|
25
|
+
if (!hasTitle) {
|
|
26
|
+
shouldSetTitle = true;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<svelte:head>
|
|
32
|
+
{#if shouldSetTitle}
|
|
33
|
+
<title>{name}</title>
|
|
34
|
+
{/if}
|
|
35
|
+
|
|
36
|
+
{#if !disablePageZoom}
|
|
37
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
38
|
+
{:else}
|
|
39
|
+
<meta name="viewport"
|
|
40
|
+
content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no, width=device-width, viewport-fit=cover" />
|
|
41
|
+
{/if}
|
|
42
|
+
|
|
43
|
+
<meta name="theme-color" content="{themeColor}">
|
|
44
|
+
<link rel="manifest" href="/manifest.json">
|
|
45
|
+
|
|
46
|
+
<meta name="mobile-web-app-capable" content="yes">
|
|
47
|
+
|
|
48
|
+
<!-- iOS-specific meta tags -->
|
|
49
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="{statusBarStyle}">
|
|
50
|
+
<meta name="apple-mobile-web-app-title" content="{shortName}">
|
|
51
|
+
</svelte:head>
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export default PWA;
|
|
2
|
+
type PWA = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
declare const PWA: import("svelte").Component<{
|
|
7
|
+
config: import("../config.typedef.js").MetaConfig;
|
|
8
|
+
}, {}, "">;
|
|
9
|
+
type $$ComponentProps = {
|
|
10
|
+
config: {
|
|
11
|
+
/**
|
|
12
|
+
* - Full app name
|
|
13
|
+
*/
|
|
14
|
+
name: string;
|
|
15
|
+
/**
|
|
16
|
+
* - Short app name (max 12 characters)
|
|
17
|
+
*/
|
|
18
|
+
shortName: string;
|
|
19
|
+
/**
|
|
20
|
+
* - App description for search engines
|
|
21
|
+
*
|
|
22
|
+
* Language and locale
|
|
23
|
+
*/
|
|
24
|
+
description: string;
|
|
25
|
+
/**
|
|
26
|
+
* Language configurations
|
|
27
|
+
*/
|
|
28
|
+
languages: Record<string, {
|
|
29
|
+
lang: string;
|
|
30
|
+
locale: string;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* - Default language code
|
|
34
|
+
*/
|
|
35
|
+
defaultLanguage: string;
|
|
36
|
+
/**
|
|
37
|
+
* - Default locale
|
|
38
|
+
*
|
|
39
|
+
* PWA theme and colors
|
|
40
|
+
*/
|
|
41
|
+
defaultLocale: string;
|
|
42
|
+
/**
|
|
43
|
+
* - Theme color
|
|
44
|
+
*/
|
|
45
|
+
backgroundAndThemeColor: string;
|
|
46
|
+
/**
|
|
47
|
+
* - Theme color for browser UI
|
|
48
|
+
*/
|
|
49
|
+
themeColor: string;
|
|
50
|
+
/**
|
|
51
|
+
* - Background color
|
|
52
|
+
*/
|
|
53
|
+
backgroundColor: string;
|
|
54
|
+
/**
|
|
55
|
+
* - iOS status bar style
|
|
56
|
+
*/
|
|
57
|
+
statusBarStyle: string;
|
|
58
|
+
/**
|
|
59
|
+
* - Screen orientation
|
|
60
|
+
*/
|
|
61
|
+
orientation: string;
|
|
62
|
+
/**
|
|
63
|
+
* - Disable pinch-to-zoom
|
|
64
|
+
*
|
|
65
|
+
* SEO images
|
|
66
|
+
*/
|
|
67
|
+
disablePageZoom: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* - Landscape SEO image URL (1200×630)
|
|
70
|
+
*/
|
|
71
|
+
SeoImageLandscape?: string | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* - Square SEO image URL (1200×1200)
|
|
74
|
+
*
|
|
75
|
+
* Favicon images (processed by imagetools)
|
|
76
|
+
*/
|
|
77
|
+
SeoImageSquare?: string | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* Processed favicon images
|
|
80
|
+
*/
|
|
81
|
+
faviconImages: Array<{
|
|
82
|
+
src: string;
|
|
83
|
+
width: number;
|
|
84
|
+
}>;
|
|
85
|
+
/**
|
|
86
|
+
* Processed apple-touch-icon images
|
|
87
|
+
*
|
|
88
|
+
* Site configuration
|
|
89
|
+
*/
|
|
90
|
+
appleTouchIcons: Array<{
|
|
91
|
+
src: string;
|
|
92
|
+
width: number;
|
|
93
|
+
}>;
|
|
94
|
+
/**
|
|
95
|
+
* Routes for sitemap.xml
|
|
96
|
+
*/
|
|
97
|
+
siteRoutes: import("../typedef.js").SitemapRoute[];
|
|
98
|
+
/**
|
|
99
|
+
* Robots.txt configuration
|
|
100
|
+
*/
|
|
101
|
+
robotsConfig: import("../typedef.js").RobotsConfig;
|
|
102
|
+
};
|
|
103
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
/**
|
|
3
|
+
* SEO component
|
|
4
|
+
*
|
|
5
|
+
* Generates SEO meta tags, Open Graph tags, and hreflang links.
|
|
6
|
+
*
|
|
7
|
+
* @typedef {import('../typedef.js').MetaConfig} MetaConfig
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* SEO component props
|
|
12
|
+
*
|
|
13
|
+
* @typedef {Object} SEOProps
|
|
14
|
+
* @property {MetaConfig} config - Configuration object
|
|
15
|
+
* @property {string} [title] - Page title (defaults to config name)
|
|
16
|
+
* @property {string} [description]
|
|
17
|
+
* Page description (defaults to config description)
|
|
18
|
+
* @property {string} [url] - Canonical URL for this page
|
|
19
|
+
* @property {string} [image]
|
|
20
|
+
* Social media preview image URL (defaults to landscape SEO image)
|
|
21
|
+
* @property {string} [imageAlt] - Alt text for social media image
|
|
22
|
+
* @property {string} [type] - Open Graph type (default: 'website')
|
|
23
|
+
* @property {string} [locale]
|
|
24
|
+
* Content locale (auto-detected from URL or defaults to config)
|
|
25
|
+
* @property {string} [siteName]
|
|
26
|
+
* Site name for Open Graph (defaults to config name)
|
|
27
|
+
* @property {Record<string, string>} [alternateUrls]
|
|
28
|
+
* Alternate language URLs for hreflang tags
|
|
29
|
+
* @property {string} [robots]
|
|
30
|
+
* Robots meta directives (e.g., 'noindex, nofollow')
|
|
31
|
+
* @property {boolean} [noAiTraining]
|
|
32
|
+
* Prevent AI training on this page content
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/** @type {SEOProps} */
|
|
36
|
+
let {
|
|
37
|
+
config,
|
|
38
|
+
title,
|
|
39
|
+
description,
|
|
40
|
+
url = undefined,
|
|
41
|
+
image,
|
|
42
|
+
imageAlt,
|
|
43
|
+
type = 'website',
|
|
44
|
+
locale = undefined,
|
|
45
|
+
siteName,
|
|
46
|
+
alternateUrls = undefined,
|
|
47
|
+
robots = undefined,
|
|
48
|
+
noAiTraining = false
|
|
49
|
+
} = $props();
|
|
50
|
+
|
|
51
|
+
// Extract config values
|
|
52
|
+
const {
|
|
53
|
+
name: defaultTitle,
|
|
54
|
+
description: defaultDescription,
|
|
55
|
+
SeoImageLandscape,
|
|
56
|
+
SeoImageSquare,
|
|
57
|
+
defaultLocale,
|
|
58
|
+
languages
|
|
59
|
+
} = config;
|
|
60
|
+
|
|
61
|
+
// Use the landscape image as default (best for most platforms)
|
|
62
|
+
const defaultSeoImage = SeoImageLandscape || undefined;
|
|
63
|
+
|
|
64
|
+
// Apply defaults from config
|
|
65
|
+
title = title ?? defaultTitle;
|
|
66
|
+
description = description ?? defaultDescription;
|
|
67
|
+
image = image ?? defaultSeoImage;
|
|
68
|
+
imageAlt = imageAlt ?? title;
|
|
69
|
+
siteName = siteName ?? defaultTitle;
|
|
70
|
+
|
|
71
|
+
// Use locale from prop or fall back to config default
|
|
72
|
+
const finalLocale = locale || defaultLocale;
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<svelte:head>
|
|
76
|
+
<!-- Page title (overrides %title% from app.html) -->
|
|
77
|
+
<title>{title}</title>
|
|
78
|
+
|
|
79
|
+
<!-- Basic SEO -->
|
|
80
|
+
<meta name="description" content={description}>
|
|
81
|
+
|
|
82
|
+
<!-- Robots directives -->
|
|
83
|
+
{#if robots}
|
|
84
|
+
<meta name="robots" content={robots}>
|
|
85
|
+
{/if}
|
|
86
|
+
|
|
87
|
+
<!-- AI training and reading restrictions -->
|
|
88
|
+
{#if noAiTraining}
|
|
89
|
+
<!-- Google AI -->
|
|
90
|
+
<meta name="google-extended" content="noindex, nofollow">
|
|
91
|
+
<!-- OpenAI (ChatGPT) -->
|
|
92
|
+
<meta name="OAI-SearchBot" content="noindex, nofollow">
|
|
93
|
+
<!-- Common Crawl -->
|
|
94
|
+
<meta name="CCBot" content="noindex, nofollow">
|
|
95
|
+
<!-- Anthropic Claude -->
|
|
96
|
+
<meta name="anthropic-ai" content="noindex, nofollow">
|
|
97
|
+
<!-- General AI crawlers -->
|
|
98
|
+
<meta name="robots" content="noai, noimageai">
|
|
99
|
+
{/if}
|
|
100
|
+
|
|
101
|
+
<!-- Open Graph / Facebook / LinkedIn / Discord -->
|
|
102
|
+
<meta property="og:type" content={type}>
|
|
103
|
+
<meta property="og:title" content={title}>
|
|
104
|
+
<meta property="og:description" content={description}>
|
|
105
|
+
<meta property="og:site_name" content={siteName}>
|
|
106
|
+
<meta property="og:locale" content={finalLocale}>
|
|
107
|
+
|
|
108
|
+
{#if url}
|
|
109
|
+
<meta property="og:url" content={url}>
|
|
110
|
+
<link rel="canonical" href={url}>
|
|
111
|
+
{/if}
|
|
112
|
+
|
|
113
|
+
{#if image}
|
|
114
|
+
<meta property="og:image" content={image}>
|
|
115
|
+
<meta property="og:image:alt" content={imageAlt}>
|
|
116
|
+
<!-- Image metadata (dimensions from preprocessor) -->
|
|
117
|
+
{#if image === SeoImageLandscape}
|
|
118
|
+
<meta property="og:image:width" content="1200">
|
|
119
|
+
<meta property="og:image:height" content="630">
|
|
120
|
+
<meta property="og:image:type" content="image/jpeg">
|
|
121
|
+
{/if}
|
|
122
|
+
{/if}
|
|
123
|
+
|
|
124
|
+
<!-- Additional square image for platforms that prefer it -->
|
|
125
|
+
{#if SeoImageSquare && SeoImageSquare !== image}
|
|
126
|
+
<meta property="og:image" content={SeoImageSquare}>
|
|
127
|
+
<meta property="og:image:width" content="1200">
|
|
128
|
+
<meta property="og:image:height" content="1200">
|
|
129
|
+
<meta property="og:image:type" content="image/jpeg">
|
|
130
|
+
{/if}
|
|
131
|
+
|
|
132
|
+
<!-- Alternate locales for Open Graph -->
|
|
133
|
+
{#each Object.entries(languages) as [code, config]}
|
|
134
|
+
{#if config.locale !== finalLocale}
|
|
135
|
+
<meta property="og:locale:alternate" content={config.locale}>
|
|
136
|
+
{/if}
|
|
137
|
+
{/each}
|
|
138
|
+
|
|
139
|
+
<!-- Alternate language URLs (hreflang) -->
|
|
140
|
+
{#if alternateUrls}
|
|
141
|
+
{#each Object.entries(alternateUrls) as [lang, href]}
|
|
142
|
+
<link rel="alternate" hreflang={lang} href={href}>
|
|
143
|
+
{/each}
|
|
144
|
+
{/if}
|
|
145
|
+
|
|
146
|
+
</svelte:head>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export default SEO;
|
|
2
|
+
type SEO = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<SEOProps>): void;
|
|
5
|
+
};
|
|
6
|
+
declare const SEO: import("svelte").Component<{
|
|
7
|
+
/**
|
|
8
|
+
* - Configuration object
|
|
9
|
+
*/
|
|
10
|
+
config: import("../config.typedef.js").MetaConfig;
|
|
11
|
+
/**
|
|
12
|
+
* - Page title (defaults to config name)
|
|
13
|
+
*/
|
|
14
|
+
title?: string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Page description (defaults to config description)
|
|
17
|
+
*/
|
|
18
|
+
description?: string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* - Canonical URL for this page
|
|
21
|
+
*/
|
|
22
|
+
url?: string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Social media preview image URL (defaults to landscape SEO image)
|
|
25
|
+
*/
|
|
26
|
+
image?: string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* - Alt text for social media image
|
|
29
|
+
*/
|
|
30
|
+
imageAlt?: string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* - Open Graph type (default: 'website')
|
|
33
|
+
*/
|
|
34
|
+
type?: string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Content locale (auto-detected from URL or defaults to config)
|
|
37
|
+
*/
|
|
38
|
+
locale?: string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Site name for Open Graph (defaults to config name)
|
|
41
|
+
*/
|
|
42
|
+
siteName?: string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Alternate language URLs for hreflang tags
|
|
45
|
+
*/
|
|
46
|
+
alternateUrls?: Record<string, string> | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Robots meta directives (e.g., 'noindex, nofollow')
|
|
49
|
+
*/
|
|
50
|
+
robots?: string | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Prevent AI training on this page content
|
|
53
|
+
*/
|
|
54
|
+
noAiTraining?: boolean | undefined;
|
|
55
|
+
}, {}, "">;
|
|
56
|
+
/**
|
|
57
|
+
* SEO component props
|
|
58
|
+
*/
|
|
59
|
+
type SEOProps = {
|
|
60
|
+
/**
|
|
61
|
+
* - Configuration object
|
|
62
|
+
*/
|
|
63
|
+
config: import("../config.typedef.js").MetaConfig;
|
|
64
|
+
/**
|
|
65
|
+
* - Page title (defaults to config name)
|
|
66
|
+
*/
|
|
67
|
+
title?: string | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Page description (defaults to config description)
|
|
70
|
+
*/
|
|
71
|
+
description?: string | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* - Canonical URL for this page
|
|
74
|
+
*/
|
|
75
|
+
url?: string | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* Social media preview image URL (defaults to landscape SEO image)
|
|
78
|
+
*/
|
|
79
|
+
image?: string | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* - Alt text for social media image
|
|
82
|
+
*/
|
|
83
|
+
imageAlt?: string | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* - Open Graph type (default: 'website')
|
|
86
|
+
*/
|
|
87
|
+
type?: string | undefined;
|
|
88
|
+
/**
|
|
89
|
+
* Content locale (auto-detected from URL or defaults to config)
|
|
90
|
+
*/
|
|
91
|
+
locale?: string | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* Site name for Open Graph (defaults to config name)
|
|
94
|
+
*/
|
|
95
|
+
siteName?: string | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* Alternate language URLs for hreflang tags
|
|
98
|
+
*/
|
|
99
|
+
alternateUrls?: Record<string, string> | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Robots meta directives (e.g., 'noindex, nofollow')
|
|
102
|
+
*/
|
|
103
|
+
robots?: string | undefined;
|
|
104
|
+
/**
|
|
105
|
+
* Prevent AI training on this page content
|
|
106
|
+
*/
|
|
107
|
+
noAiTraining?: boolean | undefined;
|
|
108
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
declare const _default: {};
|
|
2
|
+
export default _default;
|
|
3
|
+
/**
|
|
4
|
+
* App identity
|
|
5
|
+
*/
|
|
6
|
+
export type MetaConfig = {
|
|
7
|
+
/**
|
|
8
|
+
* - Full app name
|
|
9
|
+
*/
|
|
10
|
+
name: string;
|
|
11
|
+
/**
|
|
12
|
+
* - Short app name (max 12 characters)
|
|
13
|
+
*/
|
|
14
|
+
shortName: string;
|
|
15
|
+
/**
|
|
16
|
+
* - App description for search engines
|
|
17
|
+
*
|
|
18
|
+
* Language and locale
|
|
19
|
+
*/
|
|
20
|
+
description: string;
|
|
21
|
+
/**
|
|
22
|
+
* Language configurations
|
|
23
|
+
*/
|
|
24
|
+
languages: Record<string, {
|
|
25
|
+
lang: string;
|
|
26
|
+
locale: string;
|
|
27
|
+
}>;
|
|
28
|
+
/**
|
|
29
|
+
* - Default language code
|
|
30
|
+
*/
|
|
31
|
+
defaultLanguage: string;
|
|
32
|
+
/**
|
|
33
|
+
* - Default locale
|
|
34
|
+
*
|
|
35
|
+
* PWA theme and colors
|
|
36
|
+
*/
|
|
37
|
+
defaultLocale: string;
|
|
38
|
+
/**
|
|
39
|
+
* - Theme color
|
|
40
|
+
*/
|
|
41
|
+
backgroundAndThemeColor: string;
|
|
42
|
+
/**
|
|
43
|
+
* - Theme color for browser UI
|
|
44
|
+
*/
|
|
45
|
+
themeColor: string;
|
|
46
|
+
/**
|
|
47
|
+
* - Background color
|
|
48
|
+
*/
|
|
49
|
+
backgroundColor: string;
|
|
50
|
+
/**
|
|
51
|
+
* - iOS status bar style
|
|
52
|
+
*/
|
|
53
|
+
statusBarStyle: string;
|
|
54
|
+
/**
|
|
55
|
+
* - Screen orientation
|
|
56
|
+
*/
|
|
57
|
+
orientation: string;
|
|
58
|
+
/**
|
|
59
|
+
* - Disable pinch-to-zoom
|
|
60
|
+
*
|
|
61
|
+
* SEO images
|
|
62
|
+
*/
|
|
63
|
+
disablePageZoom: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* - Landscape SEO image URL (1200×630)
|
|
66
|
+
*/
|
|
67
|
+
SeoImageLandscape?: string | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* - Square SEO image URL (1200×1200)
|
|
70
|
+
*
|
|
71
|
+
* Favicon images (processed by imagetools)
|
|
72
|
+
*/
|
|
73
|
+
SeoImageSquare?: string | undefined;
|
|
74
|
+
/**
|
|
75
|
+
* Processed favicon images
|
|
76
|
+
*/
|
|
77
|
+
faviconImages: Array<{
|
|
78
|
+
src: string;
|
|
79
|
+
width: number;
|
|
80
|
+
}>;
|
|
81
|
+
/**
|
|
82
|
+
* Processed apple-touch-icon images
|
|
83
|
+
*
|
|
84
|
+
* Site configuration
|
|
85
|
+
*/
|
|
86
|
+
appleTouchIcons: Array<{
|
|
87
|
+
src: string;
|
|
88
|
+
width: number;
|
|
89
|
+
}>;
|
|
90
|
+
/**
|
|
91
|
+
* Routes for sitemap.xml
|
|
92
|
+
*/
|
|
93
|
+
siteRoutes: import("./utils/sitemap/typedef.js").SitemapRoute[];
|
|
94
|
+
/**
|
|
95
|
+
* Robots.txt configuration
|
|
96
|
+
*/
|
|
97
|
+
robotsConfig: import("./utils/robots/typedef.js").RobotsConfig;
|
|
98
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta configuration type definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} MetaConfig
|
|
7
|
+
*
|
|
8
|
+
* App identity
|
|
9
|
+
* @property {string} name - Full app name
|
|
10
|
+
* @property {string} shortName - Short app name (max 12 characters)
|
|
11
|
+
* @property {string} description - App description for search engines
|
|
12
|
+
*
|
|
13
|
+
* Language and locale
|
|
14
|
+
* @property {Record<string, {lang: string, locale: string}>} languages
|
|
15
|
+
* Language configurations
|
|
16
|
+
* @property {string} defaultLanguage - Default language code
|
|
17
|
+
* @property {string} defaultLocale - Default locale
|
|
18
|
+
*
|
|
19
|
+
* PWA theme and colors
|
|
20
|
+
* @property {string} backgroundAndThemeColor - Theme color
|
|
21
|
+
* @property {string} themeColor - Theme color for browser UI
|
|
22
|
+
* @property {string} backgroundColor - Background color
|
|
23
|
+
* @property {string} statusBarStyle - iOS status bar style
|
|
24
|
+
* @property {string} orientation - Screen orientation
|
|
25
|
+
* @property {boolean} disablePageZoom - Disable pinch-to-zoom
|
|
26
|
+
*
|
|
27
|
+
* SEO images
|
|
28
|
+
* @property {string} [SeoImageLandscape] - Landscape SEO image URL (1200×630)
|
|
29
|
+
* @property {string} [SeoImageSquare] - Square SEO image URL (1200×1200)
|
|
30
|
+
*
|
|
31
|
+
* Favicon images (processed by imagetools)
|
|
32
|
+
* @property {Array<{src: string, width: number}>} faviconImages
|
|
33
|
+
* Processed favicon images
|
|
34
|
+
* @property {Array<{src: string, width: number}>} appleTouchIcons
|
|
35
|
+
* Processed apple-touch-icon images
|
|
36
|
+
*
|
|
37
|
+
* Site configuration
|
|
38
|
+
* @property {import('./utils/sitemap/typedef.js').SitemapRoute[]} siteRoutes
|
|
39
|
+
* Routes for sitemap.xml
|
|
40
|
+
* @property {import('./utils/robots/typedef.js').RobotsConfig} robotsConfig
|
|
41
|
+
* Robots.txt configuration
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
export default {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for meta utilities
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all typedefs from utils subfolder for convenient importing
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Re-export config typedefs
|
|
8
|
+
export * from './config.typedef.js';
|
|
9
|
+
|
|
10
|
+
// Re-export robots typedefs
|
|
11
|
+
export * from './utils/robots/typedef.js';
|
|
12
|
+
|
|
13
|
+
// Re-export sitemap typedefs
|
|
14
|
+
export * from './utils/sitemap/typedef.js';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create language utilities configured with your app config
|
|
3
|
+
*
|
|
4
|
+
* @param {Object} config - Configuration object
|
|
5
|
+
* @param {Record<string, {lang: string, locale: string}>} config.languages
|
|
6
|
+
* Language configurations
|
|
7
|
+
* @param {string} config.defaultLanguage - Default language code
|
|
8
|
+
* @param {string} config.name - App name (for %title% injection)
|
|
9
|
+
* @param {string} config.description - App description
|
|
10
|
+
*
|
|
11
|
+
* @returns {{
|
|
12
|
+
* getLangFromPath: Function,
|
|
13
|
+
* injectLang: Function,
|
|
14
|
+
* handleLang: Function
|
|
15
|
+
* }}
|
|
16
|
+
*/
|
|
17
|
+
export function createLangUtils(config: {
|
|
18
|
+
languages: Record<string, {
|
|
19
|
+
lang: string;
|
|
20
|
+
locale: string;
|
|
21
|
+
}>;
|
|
22
|
+
defaultLanguage: string;
|
|
23
|
+
name: string;
|
|
24
|
+
description: string;
|
|
25
|
+
}): {
|
|
26
|
+
getLangFromPath: Function;
|
|
27
|
+
injectLang: Function;
|
|
28
|
+
handleLang: Function;
|
|
29
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create language utilities configured with your app config
|
|
3
|
+
*
|
|
4
|
+
* @param {Object} config - Configuration object
|
|
5
|
+
* @param {Record<string, {lang: string, locale: string}>} config.languages
|
|
6
|
+
* Language configurations
|
|
7
|
+
* @param {string} config.defaultLanguage - Default language code
|
|
8
|
+
* @param {string} config.name - App name (for %title% injection)
|
|
9
|
+
* @param {string} config.description - App description
|
|
10
|
+
*
|
|
11
|
+
* @returns {{
|
|
12
|
+
* getLangFromPath: Function,
|
|
13
|
+
* injectLang: Function,
|
|
14
|
+
* handleLang: Function
|
|
15
|
+
* }}
|
|
16
|
+
*/
|
|
17
|
+
export function createLangUtils(config) {
|
|
18
|
+
const { languages, defaultLanguage, name, description } = config;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extract language code from URL pathname
|
|
22
|
+
*
|
|
23
|
+
* @param {string} pathname - URL pathname (e.g., '/en/shop')
|
|
24
|
+
*
|
|
25
|
+
* @returns {{
|
|
26
|
+
* langCode: string,
|
|
27
|
+
* lang: string,
|
|
28
|
+
* locale: string
|
|
29
|
+
* }}
|
|
30
|
+
*/
|
|
31
|
+
function getLangFromPath(pathname) {
|
|
32
|
+
const match = pathname.match(/^\/([a-z]{2}(?:-[a-z]{2})?)\//i);
|
|
33
|
+
const langCode = match ? match[1].toLowerCase() : defaultLanguage;
|
|
34
|
+
|
|
35
|
+
const langConfig = languages[langCode] || languages[defaultLanguage];
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
langCode,
|
|
39
|
+
lang: langConfig.lang,
|
|
40
|
+
locale: langConfig.locale
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Transform HTML to inject language, title, and description
|
|
46
|
+
*
|
|
47
|
+
* @param {string} html - HTML string with placeholders
|
|
48
|
+
* @param {string} lang - Language code (e.g., 'en-GB')
|
|
49
|
+
*
|
|
50
|
+
* @returns {string} HTML with values injected
|
|
51
|
+
*/
|
|
52
|
+
function injectLang(html, lang) {
|
|
53
|
+
return html
|
|
54
|
+
.replace('%lang%', lang)
|
|
55
|
+
.replace('%title%', name)
|
|
56
|
+
.replace('%description%', description);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* SvelteKit hook handler for language detection
|
|
61
|
+
*
|
|
62
|
+
* @param {object} event - SvelteKit event object
|
|
63
|
+
* @param {Function} resolve - SvelteKit resolve function
|
|
64
|
+
*
|
|
65
|
+
* @returns {Promise<Response>}
|
|
66
|
+
*/
|
|
67
|
+
async function handleLang(event, resolve) {
|
|
68
|
+
const { langCode, lang, locale } = getLangFromPath(event.url.pathname);
|
|
69
|
+
|
|
70
|
+
event.locals.langCode = langCode;
|
|
71
|
+
event.locals.lang = lang;
|
|
72
|
+
event.locals.locale = locale;
|
|
73
|
+
|
|
74
|
+
return resolve(event, {
|
|
75
|
+
transformPageChunk: ({ html }) => injectLang(html, lang)
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
getLangFromPath,
|
|
81
|
+
injectLang,
|
|
82
|
+
handleLang
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { generateRobotsTxt, isHostAllowed } from "./robots/robots.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { generateRobotsTxt, isHostAllowed } from './robots/robots.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { generateSitemap } from "./sitemap/sitemap.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { generateSitemap } from './sitemap/sitemap.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta utilities for SEO, PWA, and favicons
|
|
3
|
+
*
|
|
4
|
+
* @module @hkdigital/lib-core/meta
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Language utilities
|
|
8
|
+
export { createLangUtils } from './utils/lang.js';
|
|
9
|
+
|
|
10
|
+
export { generateRobotsTxt, isHostAllowed } from './utils/robots.js';
|
|
11
|
+
export { generateSitemap } from './utils/sitemap.js';
|
package/package.json
CHANGED
package/dist/meta/robots.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { generateRobotsTxt, isHostAllowed } from "./robots/index.js";
|
package/dist/meta/robots.js
DELETED
package/dist/meta/sitemap.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { generateSitemap } from "./sitemap/index.js";
|
package/dist/meta/sitemap.js
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|