jamdesk 1.1.91 → 1.1.92
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jamdesk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.92",
|
|
4
4
|
"description": "CLI for Jamdesk — build, preview, and deploy documentation sites from MDX. Dev server with hot reload, 50+ components, OpenAPI support, AI search, and Mintlify migration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jamdesk",
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
toHreflang,
|
|
15
15
|
} from '@/lib/language-utils';
|
|
16
16
|
import { useLinkPrefix } from '@/lib/link-prefix-context';
|
|
17
|
+
import { useProjectSlug } from '@/lib/project-slug-context';
|
|
17
18
|
import { getUiStrings } from '@/lib/ui-strings';
|
|
18
19
|
|
|
19
20
|
interface LanguageSelectorProps {
|
|
@@ -35,6 +36,7 @@ export function LanguageSelector({
|
|
|
35
36
|
}: LanguageSelectorProps) {
|
|
36
37
|
const router = useRouter();
|
|
37
38
|
const linkPrefix = useLinkPrefix();
|
|
39
|
+
const projectSlug = useProjectSlug();
|
|
38
40
|
const pathname = usePathname() ?? '';
|
|
39
41
|
const ui = getUiStrings(extractLanguageFromPath(pathname || '/'));
|
|
40
42
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -83,7 +85,7 @@ export function LanguageSelector({
|
|
|
83
85
|
// fight the user's in-session navigation choices (they may have explicitly
|
|
84
86
|
// switched languages this session, which we don't want to revert).
|
|
85
87
|
useEffect(() => {
|
|
86
|
-
const savedPref = getLanguagePreference();
|
|
88
|
+
const savedPref = getLanguagePreference(projectSlug);
|
|
87
89
|
if (savedPref && currentLanguage && savedPref !== currentLanguage.code) {
|
|
88
90
|
// Only redirect if on default language URL (no explicit language in path)
|
|
89
91
|
const hasLangInPath = languages.some(
|
|
@@ -117,7 +119,7 @@ export function LanguageSelector({
|
|
|
117
119
|
return;
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
saveLanguagePreference(lang.code);
|
|
122
|
+
saveLanguagePreference(lang.code, projectSlug);
|
|
121
123
|
|
|
122
124
|
const basePath = transformLanguagePath(
|
|
123
125
|
pathname || '/docs',
|
|
@@ -130,7 +132,7 @@ export function LanguageSelector({
|
|
|
130
132
|
onNavigate(url);
|
|
131
133
|
router.push(url);
|
|
132
134
|
},
|
|
133
|
-
[isNavigating, currentLanguage?.code, actualDefault, pathname, linkPrefix, router, onNavigate]
|
|
135
|
+
[isNavigating, currentLanguage?.code, actualDefault, pathname, linkPrefix, router, onNavigate, projectSlug]
|
|
134
136
|
);
|
|
135
137
|
|
|
136
138
|
// Handle keyboard navigation
|
|
@@ -419,17 +419,28 @@ export function resolveLocaleWithLoweredSet(
|
|
|
419
419
|
}
|
|
420
420
|
|
|
421
421
|
/**
|
|
422
|
-
* localStorage key for language
|
|
422
|
+
* localStorage key prefix for project-scoped language preferences. The actual
|
|
423
|
+
* key is `${LANGUAGE_STORAGE_KEY_PREFIX}${slug}`. Scoping by project prevents
|
|
424
|
+
* a `cn` preference saved on Project A from forcing a `/cn/...` redirect on
|
|
425
|
+
* Project B that only declares `zh-Hans` (would 404).
|
|
423
426
|
*/
|
|
424
|
-
export const
|
|
427
|
+
export const LANGUAGE_STORAGE_KEY_PREFIX = 'jamdesk-language-preference:';
|
|
428
|
+
|
|
429
|
+
/** Legacy unscoped key — read-once for one-time migration to per-project storage. */
|
|
430
|
+
const LEGACY_LANGUAGE_STORAGE_KEY = 'jamdesk-language-preference';
|
|
431
|
+
|
|
432
|
+
function storageKey(projectSlug: string): string {
|
|
433
|
+
return `${LANGUAGE_STORAGE_KEY_PREFIX}${projectSlug}`;
|
|
434
|
+
}
|
|
425
435
|
|
|
426
436
|
/**
|
|
427
|
-
* Save language preference to localStorage
|
|
428
|
-
* Handles private browsing mode gracefully
|
|
437
|
+
* Save language preference to localStorage, scoped to project slug.
|
|
438
|
+
* Handles private browsing mode gracefully.
|
|
429
439
|
*/
|
|
430
|
-
export function saveLanguagePreference(code: LanguageCode): void {
|
|
440
|
+
export function saveLanguagePreference(code: LanguageCode, projectSlug: string): void {
|
|
441
|
+
if (!projectSlug) return;
|
|
431
442
|
try {
|
|
432
|
-
localStorage.setItem(
|
|
443
|
+
localStorage.setItem(storageKey(projectSlug), code);
|
|
433
444
|
} catch {
|
|
434
445
|
// localStorage not available (private browsing, etc.)
|
|
435
446
|
// Fail silently
|
|
@@ -437,15 +448,24 @@ export function saveLanguagePreference(code: LanguageCode): void {
|
|
|
437
448
|
}
|
|
438
449
|
|
|
439
450
|
/**
|
|
440
|
-
* Get saved language preference from localStorage
|
|
441
|
-
* Returns undefined if not set or localStorage unavailable
|
|
451
|
+
* Get saved language preference for a project from localStorage.
|
|
452
|
+
* Returns undefined if not set or localStorage unavailable.
|
|
442
453
|
*/
|
|
443
|
-
export function getLanguagePreference(): LanguageCode | undefined {
|
|
454
|
+
export function getLanguagePreference(projectSlug: string): LanguageCode | undefined {
|
|
455
|
+
if (!projectSlug) return undefined;
|
|
444
456
|
try {
|
|
445
|
-
const saved = localStorage.getItem(
|
|
457
|
+
const saved = localStorage.getItem(storageKey(projectSlug));
|
|
446
458
|
if (saved && isValidLanguageCode(saved)) {
|
|
447
459
|
return saved as LanguageCode;
|
|
448
460
|
}
|
|
461
|
+
// One-time read of legacy unscoped key — promotes it to the per-project
|
|
462
|
+
// slot the first time a returning user visits and clears the legacy entry.
|
|
463
|
+
const legacy = localStorage.getItem(LEGACY_LANGUAGE_STORAGE_KEY);
|
|
464
|
+
if (legacy && isValidLanguageCode(legacy)) {
|
|
465
|
+
localStorage.setItem(storageKey(projectSlug), legacy);
|
|
466
|
+
localStorage.removeItem(LEGACY_LANGUAGE_STORAGE_KEY);
|
|
467
|
+
return legacy as LanguageCode;
|
|
468
|
+
}
|
|
449
469
|
} catch {
|
|
450
470
|
// localStorage not available
|
|
451
471
|
}
|
|
@@ -453,11 +473,12 @@ export function getLanguagePreference(): LanguageCode | undefined {
|
|
|
453
473
|
}
|
|
454
474
|
|
|
455
475
|
/**
|
|
456
|
-
* Clear saved language preference
|
|
476
|
+
* Clear saved language preference for a project.
|
|
457
477
|
*/
|
|
458
|
-
export function clearLanguagePreference(): void {
|
|
478
|
+
export function clearLanguagePreference(projectSlug: string): void {
|
|
479
|
+
if (!projectSlug) return;
|
|
459
480
|
try {
|
|
460
|
-
localStorage.removeItem(
|
|
481
|
+
localStorage.removeItem(storageKey(projectSlug));
|
|
461
482
|
} catch {
|
|
462
483
|
// localStorage not available
|
|
463
484
|
}
|
package/vendored/lib/seo.ts
CHANGED
|
@@ -188,7 +188,7 @@ function getLanguagePagePaths(langConfig: LanguageConfig): Set<string> {
|
|
|
188
188
|
* @param languages - Array of language configurations from docs.json
|
|
189
189
|
* @returns Record of language code to URL for alternates.languages
|
|
190
190
|
*/
|
|
191
|
-
function buildHreflangAlternates(
|
|
191
|
+
export function buildHreflangAlternates(
|
|
192
192
|
baseUrl: string,
|
|
193
193
|
pagePath: string,
|
|
194
194
|
languages: LanguageConfig[]
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* for ISR projects. These are uploaded to R2 alongside the MDX content.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { NavigationConfig } from './docs-types.js';
|
|
8
|
+
import type { NavigationConfig, LanguageConfig } from './docs-types.js';
|
|
9
9
|
import { RECURSE_KEYS } from './enhance-navigation.js';
|
|
10
10
|
import { filterVisibility } from './visibility-filter.js';
|
|
11
11
|
import {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
resolveLocaleFromPath,
|
|
14
14
|
resolveLocaleWithLoweredSet,
|
|
15
15
|
} from './language-utils.js';
|
|
16
|
+
import { buildHreflangAlternates } from './seo.js';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Page metadata for artifact generation.
|
|
@@ -44,6 +45,13 @@ export interface SitemapOptions {
|
|
|
44
45
|
hostAtDocs?: boolean;
|
|
45
46
|
/** Block all crawlers - generates empty sitemap */
|
|
46
47
|
noindex?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Language configurations from docs.json navigation. When provided, the
|
|
50
|
+
* sitemap emits `<xhtml:link rel="alternate" hreflang>` siblings per URL
|
|
51
|
+
* for every language that declares the page — Google's recommended sitemap
|
|
52
|
+
* localization signal (in addition to in-page hreflang link tags).
|
|
53
|
+
*/
|
|
54
|
+
languages?: LanguageConfig[];
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
/**
|
|
@@ -53,7 +61,7 @@ export interface SitemapOptions {
|
|
|
53
61
|
* @returns XML string
|
|
54
62
|
*/
|
|
55
63
|
export function generateSitemap(options: SitemapOptions): string {
|
|
56
|
-
const { baseUrl, pages, hostAtDocs = false, noindex = false } = options;
|
|
64
|
+
const { baseUrl, pages, hostAtDocs = false, noindex = false, languages } = options;
|
|
57
65
|
|
|
58
66
|
if (noindex) {
|
|
59
67
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -62,6 +70,11 @@ export function generateSitemap(options: SitemapOptions): string {
|
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
const urlPrefix = hostAtDocs ? '/docs' : '';
|
|
73
|
+
const hreflangBaseUrl = hostAtDocs ? `${baseUrl}/docs` : baseUrl;
|
|
74
|
+
const multiLang = languages && languages.length > 1;
|
|
75
|
+
const urlsetAttrs = multiLang
|
|
76
|
+
? 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml"'
|
|
77
|
+
: 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"';
|
|
65
78
|
|
|
66
79
|
const entries = pages
|
|
67
80
|
.filter(p => !p.noindex && !p.hidden)
|
|
@@ -70,16 +83,32 @@ export function generateSitemap(options: SitemapOptions): string {
|
|
|
70
83
|
const lastmod = p.lastModified || new Date().toISOString().split('T')[0];
|
|
71
84
|
const priority = p.path === 'introduction' ? '1.0' : '0.8';
|
|
72
85
|
|
|
86
|
+
// Per Google's spec, emit hreflang siblings only when the page has
|
|
87
|
+
// translations. buildHreflangAlternates already filters to languages
|
|
88
|
+
// whose navigation declares this page, so we won't point at 404s.
|
|
89
|
+
let hreflangLinks = '';
|
|
90
|
+
if (multiLang) {
|
|
91
|
+
const alternates = buildHreflangAlternates(hreflangBaseUrl, p.path, languages);
|
|
92
|
+
if (alternates) {
|
|
93
|
+
hreflangLinks = Object.entries(alternates)
|
|
94
|
+
.map(
|
|
95
|
+
([tag, href]) =>
|
|
96
|
+
`\n <xhtml:link rel="alternate" hreflang="${tag}" href="${href}"/>`,
|
|
97
|
+
)
|
|
98
|
+
.join('');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
73
102
|
return ` <url>
|
|
74
103
|
<loc>${url}</loc>
|
|
75
104
|
<lastmod>${lastmod}</lastmod>
|
|
76
105
|
<changefreq>weekly</changefreq>
|
|
77
|
-
<priority>${priority}</priority
|
|
106
|
+
<priority>${priority}</priority>${hreflangLinks}
|
|
78
107
|
</url>`;
|
|
79
108
|
});
|
|
80
109
|
|
|
81
110
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
82
|
-
<urlset
|
|
111
|
+
<urlset ${urlsetAttrs}>
|
|
83
112
|
${entries.join('\n')}
|
|
84
113
|
</urlset>`;
|
|
85
114
|
}
|
|
@@ -353,6 +382,8 @@ export interface GenerateAllOptions {
|
|
|
353
382
|
rssPages?: RssPageInfo[];
|
|
354
383
|
/** Pages with full content (for llms-full.txt generation) */
|
|
355
384
|
llmsFullPages?: LlmsFullPageInfo[];
|
|
385
|
+
/** Language configurations (forwarded to sitemap for hreflang siblings) */
|
|
386
|
+
languages?: LanguageConfig[];
|
|
356
387
|
}
|
|
357
388
|
|
|
358
389
|
/**
|
|
@@ -374,10 +405,10 @@ export interface GeneratedArtifacts {
|
|
|
374
405
|
*/
|
|
375
406
|
export function generateAllArtifacts(options: GenerateAllOptions): GeneratedArtifacts {
|
|
376
407
|
const {
|
|
377
|
-
baseUrl, name, description, pages, hostAtDocs, noindex, rssPages, llmsFullPages,
|
|
408
|
+
baseUrl, name, description, pages, hostAtDocs, noindex, rssPages, llmsFullPages, languages,
|
|
378
409
|
} = options;
|
|
379
410
|
|
|
380
|
-
const sitemap = generateSitemap({ baseUrl, pages, hostAtDocs, noindex });
|
|
411
|
+
const sitemap = generateSitemap({ baseUrl, pages, hostAtDocs, noindex, languages });
|
|
381
412
|
const llmsTxt = generateLlmsTxt({
|
|
382
413
|
name, description, baseUrl, pages, hostAtDocs, noindex,
|
|
383
414
|
});
|