@djangocfg/nextjs 2.1.110 → 2.1.111
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/README.md +176 -7
- package/dist/config/index.d.mts +16 -1
- package/dist/config/index.mjs +83 -14
- package/dist/config/index.mjs.map +1 -1
- package/dist/i18n/client.d.mts +123 -0
- package/dist/i18n/client.mjs +104 -0
- package/dist/i18n/client.mjs.map +1 -0
- package/dist/i18n/components.d.mts +22 -0
- package/dist/i18n/components.mjs +133 -0
- package/dist/i18n/components.mjs.map +1 -0
- package/dist/i18n/index.d.mts +17 -0
- package/dist/i18n/index.mjs +269 -0
- package/dist/i18n/index.mjs.map +1 -0
- package/dist/i18n/navigation.d.mts +1095 -0
- package/dist/i18n/navigation.mjs +45 -0
- package/dist/i18n/navigation.mjs.map +1 -0
- package/dist/i18n/plugin.d.mts +41 -0
- package/dist/i18n/plugin.mjs +17 -0
- package/dist/i18n/plugin.mjs.map +1 -0
- package/dist/i18n/provider.d.mts +18 -0
- package/dist/i18n/provider.mjs +54 -0
- package/dist/i18n/provider.mjs.map +1 -0
- package/dist/i18n/proxy.d.mts +40 -0
- package/dist/i18n/proxy.mjs +42 -0
- package/dist/i18n/proxy.mjs.map +1 -0
- package/dist/i18n/request.d.mts +42 -0
- package/dist/i18n/request.mjs +63 -0
- package/dist/i18n/request.mjs.map +1 -0
- package/dist/i18n/routing.d.mts +79 -0
- package/dist/i18n/routing.mjs +33 -0
- package/dist/i18n/routing.mjs.map +1 -0
- package/dist/i18n/server.d.mts +90 -0
- package/dist/i18n/server.mjs +79 -0
- package/dist/i18n/server.mjs.map +1 -0
- package/dist/index.d.mts +3 -1
- package/dist/index.mjs +176 -30
- package/dist/index.mjs.map +1 -1
- package/dist/sitemap/index.d.mts +22 -3
- package/dist/sitemap/index.mjs +92 -15
- package/dist/sitemap/index.mjs.map +1 -1
- package/dist/types-Cy349X20.d.mts +60 -0
- package/package.json +54 -4
- package/src/config/constants.ts +1 -0
- package/src/config/createNextConfig.ts +39 -17
- package/src/i18n/client.ts +221 -0
- package/src/i18n/components/LocaleSwitcher.tsx +124 -0
- package/src/i18n/components/index.ts +7 -0
- package/src/i18n/index.ts +149 -0
- package/src/i18n/navigation.ts +90 -0
- package/src/i18n/plugin.ts +66 -0
- package/src/i18n/provider.tsx +91 -0
- package/src/i18n/proxy.ts +81 -0
- package/src/i18n/request.ts +141 -0
- package/src/i18n/routing.ts +84 -0
- package/src/i18n/server.ts +175 -0
- package/src/i18n/types.ts +88 -0
- package/src/sitemap/generator.ts +84 -9
- package/src/sitemap/index.ts +1 -1
- package/src/sitemap/route.ts +71 -8
- package/src/sitemap/types.ts +9 -0
package/dist/sitemap/index.d.mts
CHANGED
|
@@ -5,11 +5,19 @@ import { S as SitemapUrl, C as ChangeFreq } from '../types-CwhXnEbK.mjs';
|
|
|
5
5
|
* Sitemap types
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
interface SitemapI18nOptions {
|
|
9
|
+
/** Supported locales (e.g., ['en', 'ru', 'ko']) */
|
|
10
|
+
locales: string[];
|
|
11
|
+
/** Default locale for x-default hreflang */
|
|
12
|
+
defaultLocale: string;
|
|
13
|
+
}
|
|
8
14
|
interface SitemapGeneratorOptions {
|
|
9
15
|
siteUrl: string;
|
|
10
16
|
staticPages?: SitemapUrl[];
|
|
11
17
|
dynamicPages?: (() => Promise<SitemapUrl[]>) | SitemapUrl[];
|
|
12
18
|
cacheControl?: string;
|
|
19
|
+
/** i18n configuration for hreflang support */
|
|
20
|
+
i18n?: SitemapI18nOptions;
|
|
13
21
|
}
|
|
14
22
|
interface SitemapRoute {
|
|
15
23
|
path: string;
|
|
@@ -33,7 +41,6 @@ interface SitemapRoute {
|
|
|
33
41
|
* { loc: '/about', changefreq: 'monthly', priority: 0.8 },
|
|
34
42
|
* ],
|
|
35
43
|
* dynamicPages: async () => {
|
|
36
|
-
* // Fetch dynamic pages from API
|
|
37
44
|
* const posts = await fetchPosts();
|
|
38
45
|
* return posts.map(post => ({
|
|
39
46
|
* loc: `/posts/${post.slug}`,
|
|
@@ -42,6 +49,11 @@ interface SitemapRoute {
|
|
|
42
49
|
* priority: 0.7,
|
|
43
50
|
* }));
|
|
44
51
|
* },
|
|
52
|
+
* // i18n support with hreflang
|
|
53
|
+
* i18n: {
|
|
54
|
+
* locales: ['en', 'ru', 'ko'],
|
|
55
|
+
* defaultLocale: 'en',
|
|
56
|
+
* },
|
|
45
57
|
* });
|
|
46
58
|
* ```
|
|
47
59
|
*/
|
|
@@ -52,15 +64,22 @@ declare function createSitemapHandler(options: SitemapGeneratorOptions): () => P
|
|
|
52
64
|
* Sitemap Generator
|
|
53
65
|
*
|
|
54
66
|
* Generates XML sitemap from configuration
|
|
67
|
+
* Supports i18n with hreflang tags for multilingual sites
|
|
55
68
|
*/
|
|
56
69
|
|
|
70
|
+
interface GenerateSitemapXmlOptions {
|
|
71
|
+
urls: SitemapUrl[];
|
|
72
|
+
i18n?: SitemapI18nOptions;
|
|
73
|
+
siteUrl: string;
|
|
74
|
+
}
|
|
57
75
|
/**
|
|
58
76
|
* Generate XML sitemap string from URLs
|
|
77
|
+
* Supports i18n with hreflang alternate links
|
|
59
78
|
*/
|
|
60
|
-
declare function generateSitemapXml(
|
|
79
|
+
declare function generateSitemapXml(urlsOrOptions: SitemapUrl[] | GenerateSitemapXmlOptions): string;
|
|
61
80
|
/**
|
|
62
81
|
* Normalize URL (ensure absolute)
|
|
63
82
|
*/
|
|
64
83
|
declare function normalizeUrl(url: string, siteUrl: string): string;
|
|
65
84
|
|
|
66
|
-
export { type SitemapGeneratorOptions, type SitemapRoute, createSitemapHandler, generateSitemapXml, normalizeUrl };
|
|
85
|
+
export { type SitemapGeneratorOptions, type SitemapI18nOptions, type SitemapRoute, createSitemapHandler, generateSitemapXml, normalizeUrl };
|
package/dist/sitemap/index.mjs
CHANGED
|
@@ -2,22 +2,58 @@
|
|
|
2
2
|
import { NextResponse } from "next/server";
|
|
3
3
|
|
|
4
4
|
// src/sitemap/generator.ts
|
|
5
|
-
function generateSitemapXml(
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
function generateSitemapXml(urlsOrOptions) {
|
|
6
|
+
const isOptionsObject = !Array.isArray(urlsOrOptions);
|
|
7
|
+
const urls = isOptionsObject ? urlsOrOptions.urls : urlsOrOptions;
|
|
8
|
+
const i18n = isOptionsObject ? urlsOrOptions.i18n : void 0;
|
|
9
|
+
const siteUrl = isOptionsObject ? urlsOrOptions.siteUrl : "";
|
|
10
|
+
const namespaces = i18n ? `xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
|
11
|
+
xmlns:xhtml="http://www.w3.org/1999/xhtml"
|
|
12
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
13
|
+
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
|
|
14
|
+
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"` : `xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
|
8
15
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
9
16
|
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
|
|
10
|
-
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"`;
|
|
18
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
19
|
+
<urlset ${namespaces}>
|
|
20
|
+
${urls.map(({ loc, lastmod, changefreq, priority }) => {
|
|
21
|
+
const hreflangLinks = i18n ? generateHreflangLinks(loc, i18n, siteUrl) : "";
|
|
22
|
+
return ` <url>
|
|
23
|
+
<loc>${escapeXml(loc)}</loc>${hreflangLinks}
|
|
14
24
|
${lastmod ? `<lastmod>${formatDate(lastmod)}</lastmod>` : ""}
|
|
15
25
|
${changefreq ? `<changefreq>${changefreq}</changefreq>` : ""}
|
|
16
26
|
${priority !== void 0 ? `<priority>${priority.toFixed(1)}</priority>` : ""}
|
|
17
|
-
</url
|
|
18
|
-
).join("\n")}
|
|
27
|
+
</url>`;
|
|
28
|
+
}).join("\n")}
|
|
19
29
|
</urlset>`;
|
|
20
30
|
}
|
|
31
|
+
function generateHreflangLinks(loc, i18n, siteUrl) {
|
|
32
|
+
const { locales, defaultLocale } = i18n;
|
|
33
|
+
const baseSiteUrl = siteUrl.endsWith("/") ? siteUrl.slice(0, -1) : siteUrl;
|
|
34
|
+
let path = loc.replace(baseSiteUrl, "");
|
|
35
|
+
for (const locale of locales) {
|
|
36
|
+
const localePrefix = `/${locale}`;
|
|
37
|
+
if (path === localePrefix || path.startsWith(`${localePrefix}/`)) {
|
|
38
|
+
path = path.slice(localePrefix.length) || "/";
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const links = [];
|
|
43
|
+
for (const locale of locales) {
|
|
44
|
+
const localePath = path === "/" ? `/${locale}` : `/${locale}${path}`;
|
|
45
|
+
const fullUrl = `${baseSiteUrl}${localePath}`;
|
|
46
|
+
links.push(
|
|
47
|
+
` <xhtml:link rel="alternate" hreflang="${locale}" href="${escapeXml(fullUrl)}"/>`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
const defaultPath = path === "/" ? `/${defaultLocale}` : `/${defaultLocale}${path}`;
|
|
51
|
+
const defaultUrl = `${baseSiteUrl}${defaultPath}`;
|
|
52
|
+
links.push(
|
|
53
|
+
` <xhtml:link rel="alternate" hreflang="x-default" href="${escapeXml(defaultUrl)}"/>`
|
|
54
|
+
);
|
|
55
|
+
return "\n" + links.join("\n");
|
|
56
|
+
}
|
|
21
57
|
function formatDate(date) {
|
|
22
58
|
if (typeof date === "string") {
|
|
23
59
|
return date;
|
|
@@ -42,7 +78,8 @@ function createSitemapHandler(options) {
|
|
|
42
78
|
siteUrl,
|
|
43
79
|
staticPages = [],
|
|
44
80
|
dynamicPages = [],
|
|
45
|
-
cacheControl = "public, s-maxage=86400, stale-while-revalidate"
|
|
81
|
+
cacheControl = "public, s-maxage=86400, stale-while-revalidate",
|
|
82
|
+
i18n
|
|
46
83
|
} = options;
|
|
47
84
|
return async function GET() {
|
|
48
85
|
const urls = [...staticPages];
|
|
@@ -54,11 +91,20 @@ function createSitemapHandler(options) {
|
|
|
54
91
|
urls.push(...dynamicPages);
|
|
55
92
|
}
|
|
56
93
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
94
|
+
let expandedUrls;
|
|
95
|
+
if (i18n) {
|
|
96
|
+
expandedUrls = expandUrlsForLocales(urls, i18n.locales, siteUrl);
|
|
97
|
+
} else {
|
|
98
|
+
expandedUrls = urls.map((url) => ({
|
|
99
|
+
...url,
|
|
100
|
+
loc: normalizeUrl(url.loc, siteUrl)
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
const sitemap = generateSitemapXml({
|
|
104
|
+
urls: expandedUrls,
|
|
105
|
+
i18n,
|
|
106
|
+
siteUrl
|
|
107
|
+
});
|
|
62
108
|
return new NextResponse(sitemap, {
|
|
63
109
|
status: 200,
|
|
64
110
|
headers: {
|
|
@@ -68,6 +114,37 @@ function createSitemapHandler(options) {
|
|
|
68
114
|
});
|
|
69
115
|
};
|
|
70
116
|
}
|
|
117
|
+
function expandUrlsForLocales(urls, locales, siteUrl) {
|
|
118
|
+
const baseSiteUrl = siteUrl.endsWith("/") ? siteUrl.slice(0, -1) : siteUrl;
|
|
119
|
+
const expandedUrls = [];
|
|
120
|
+
for (const url of urls) {
|
|
121
|
+
let path = url.loc;
|
|
122
|
+
if (path.startsWith(baseSiteUrl)) {
|
|
123
|
+
path = path.replace(baseSiteUrl, "");
|
|
124
|
+
}
|
|
125
|
+
if (!path.startsWith("/")) {
|
|
126
|
+
path = "/" + path;
|
|
127
|
+
}
|
|
128
|
+
const hasLocalePrefix = locales.some(
|
|
129
|
+
(locale) => path === `/${locale}` || path.startsWith(`/${locale}/`)
|
|
130
|
+
);
|
|
131
|
+
if (hasLocalePrefix) {
|
|
132
|
+
expandedUrls.push({
|
|
133
|
+
...url,
|
|
134
|
+
loc: normalizeUrl(path, siteUrl)
|
|
135
|
+
});
|
|
136
|
+
} else {
|
|
137
|
+
for (const locale of locales) {
|
|
138
|
+
const localePath = path === "/" ? `/${locale}` : `/${locale}${path}`;
|
|
139
|
+
expandedUrls.push({
|
|
140
|
+
...url,
|
|
141
|
+
loc: `${baseSiteUrl}${localePath}`
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return expandedUrls;
|
|
147
|
+
}
|
|
71
148
|
export {
|
|
72
149
|
createSitemapHandler,
|
|
73
150
|
generateSitemapXml,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/sitemap/route.ts","../../src/sitemap/generator.ts"],"sourcesContent":["/**\n * Sitemap Route Handler for Next.js App Router\n *\n * Usage:\n * ```tsx\n * // app/sitemap.ts\n * import { createSitemapHandler } from '@djangocfg/nextjs/sitemap';\n *\n * export default createSitemapHandler({\n * siteUrl: 'https://example.com',\n * staticPages: [\n * { loc: '/', changefreq: 'daily', priority: 1.0 },\n * { loc: '/about', changefreq: 'monthly', priority: 0.8 },\n * ],\n * dynamicPages: async () => {\n * // Fetch dynamic pages from API\n * const posts = await fetchPosts();\n * return posts.map(post => ({\n * loc: `/posts/${post.slug}`,\n * lastmod: post.updatedAt,\n * changefreq: 'weekly',\n * priority: 0.7,\n * }));\n * },\n * });\n * ```\n */\n\nimport { NextResponse } from 'next/server';\n\nimport { generateSitemapXml, normalizeUrl } from './generator';\n\nimport type { SitemapGeneratorOptions } from './types';\nimport type { SitemapUrl } from '../types';\n\nexport function createSitemapHandler(options: SitemapGeneratorOptions) {\n const {\n siteUrl,\n staticPages = [],\n dynamicPages = [],\n cacheControl = 'public, s-maxage=86400, stale-while-revalidate',\n } = options;\n\n return async function GET() {\n const urls: SitemapUrl[] = [...staticPages];\n\n // Add dynamic pages\n if (dynamicPages) {\n if (typeof dynamicPages === 'function') {\n const dynamicUrls = await dynamicPages();\n urls.push(...dynamicUrls);\n } else {\n urls.push(...dynamicPages);\n }\n }\n\n // Normalize all URLs\n const normalizedUrls = urls.map((url) => ({\n ...url,\n loc: normalizeUrl(url.loc, siteUrl),\n }));\n\n // Generate XML\n const sitemap = generateSitemapXml(normalizedUrls);\n\n // Return response\n return new NextResponse(sitemap, {\n status: 200,\n headers: {\n 'Content-Type': 'application/xml',\n 'Cache-Control': cacheControl,\n },\n });\n };\n}\n\n","/**\n * Sitemap Generator\n *\n * Generates XML sitemap from configuration\n */\n\nimport type { SitemapUrl } from '../types';\n\n/**\n * Generate XML sitemap string from URLs\n */\nexport function generateSitemapXml(urls: SitemapUrl[]): string {\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9\n http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd\">\n${urls\n .map(\n ({ loc, lastmod, changefreq, priority }) => ` <url>\n <loc>${escapeXml(loc)}</loc>\n ${lastmod ? `<lastmod>${formatDate(lastmod)}</lastmod>` : ''}\n ${changefreq ? `<changefreq>${changefreq}</changefreq>` : ''}\n ${priority !== undefined ? `<priority>${priority.toFixed(1)}</priority>` : ''}\n </url>`\n )\n .join('\\n')}\n</urlset>`;\n}\n\n/**\n * Format date for sitemap (ISO 8601)\n */\nfunction formatDate(date: string | Date): string {\n if (typeof date === 'string') {\n return date;\n }\n return date.toISOString().split('T')[0];\n}\n\n/**\n * Escape XML special characters\n */\nfunction escapeXml(unsafe: string): string {\n return unsafe\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Normalize URL (ensure absolute)\n */\nexport function normalizeUrl(url: string, siteUrl: string): string {\n if (url.startsWith('http://') || url.startsWith('https://')) {\n return url;\n }\n const baseUrl = siteUrl.endsWith('/') ? siteUrl.slice(0, -1) : siteUrl;\n const path = url.startsWith('/') ? url : `/${url}`;\n return `${baseUrl}${path}`;\n}\n\n"],"mappings":";AA4BA,SAAS,oBAAoB;;;ACjBtB,SAAS,mBAAmB,MAA4B;AAC7D,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,EAKP,KACC;AAAA,IACC,CAAC,EAAE,KAAK,SAAS,YAAY,SAAS,MAAM;AAAA,WACrC,UAAU,GAAG,CAAC;AAAA,MACnB,UAAU,YAAY,WAAW,OAAO,CAAC,eAAe,EAAE;AAAA,MAC1D,aAAa,eAAe,UAAU,kBAAkB,EAAE;AAAA,MAC1D,aAAa,SAAY,aAAa,SAAS,QAAQ,CAAC,CAAC,gBAAgB,EAAE;AAAA;AAAA,EAE/E,EACC,KAAK,IAAI,CAAC;AAAA;AAEb;AAKA,SAAS,WAAW,MAA6B;AAC/C,MAAI,OAAO,SAAS,UAAU;AAC5B,WAAO;AAAA,EACT;AACA,SAAO,KAAK,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AACxC;AAKA,SAAS,UAAU,QAAwB;AACzC,SAAO,OACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,QAAQ;AAC3B;AAKO,SAAS,aAAa,KAAa,SAAyB;AACjE,MAAI,IAAI,WAAW,SAAS,KAAK,IAAI,WAAW,UAAU,GAAG;AAC3D,WAAO;AAAA,EACT;AACA,QAAM,UAAU,QAAQ,SAAS,GAAG,IAAI,QAAQ,MAAM,GAAG,EAAE,IAAI;AAC/D,QAAM,OAAO,IAAI,WAAW,GAAG,IAAI,MAAM,IAAI,GAAG;AAChD,SAAO,GAAG,OAAO,GAAG,IAAI;AAC1B;;;AD3BO,SAAS,qBAAqB,SAAkC;AACrE,QAAM;AAAA,IACJ;AAAA,IACA,cAAc,CAAC;AAAA,IACf,eAAe,CAAC;AAAA,IAChB,eAAe;AAAA,EACjB,IAAI;AAEJ,SAAO,eAAe,MAAM;AAC1B,UAAM,OAAqB,CAAC,GAAG,WAAW;AAG1C,QAAI,cAAc;AAChB,UAAI,OAAO,iBAAiB,YAAY;AACtC,cAAM,cAAc,MAAM,aAAa;AACvC,aAAK,KAAK,GAAG,WAAW;AAAA,MAC1B,OAAO;AACL,aAAK,KAAK,GAAG,YAAY;AAAA,MAC3B;AAAA,IACF;AAGA,UAAM,iBAAiB,KAAK,IAAI,CAAC,SAAS;AAAA,MACxC,GAAG;AAAA,MACH,KAAK,aAAa,IAAI,KAAK,OAAO;AAAA,IACpC,EAAE;AAGF,UAAM,UAAU,mBAAmB,cAAc;AAGjD,WAAO,IAAI,aAAa,SAAS;AAAA,MAC/B,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,MACnB;AAAA,IACF,CAAC;AAAA,EACH;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/sitemap/route.ts","../../src/sitemap/generator.ts"],"sourcesContent":["/**\n * Sitemap Route Handler for Next.js App Router\n *\n * Usage:\n * ```tsx\n * // app/sitemap.ts\n * import { createSitemapHandler } from '@djangocfg/nextjs/sitemap';\n *\n * export default createSitemapHandler({\n * siteUrl: 'https://example.com',\n * staticPages: [\n * { loc: '/', changefreq: 'daily', priority: 1.0 },\n * { loc: '/about', changefreq: 'monthly', priority: 0.8 },\n * ],\n * dynamicPages: async () => {\n * const posts = await fetchPosts();\n * return posts.map(post => ({\n * loc: `/posts/${post.slug}`,\n * lastmod: post.updatedAt,\n * changefreq: 'weekly',\n * priority: 0.7,\n * }));\n * },\n * // i18n support with hreflang\n * i18n: {\n * locales: ['en', 'ru', 'ko'],\n * defaultLocale: 'en',\n * },\n * });\n * ```\n */\n\nimport { NextResponse } from 'next/server';\n\nimport { generateSitemapXml, normalizeUrl } from './generator';\n\nimport type { SitemapGeneratorOptions } from './types';\nimport type { SitemapUrl } from '../types';\n\nexport function createSitemapHandler(options: SitemapGeneratorOptions) {\n const {\n siteUrl,\n staticPages = [],\n dynamicPages = [],\n cacheControl = 'public, s-maxage=86400, stale-while-revalidate',\n i18n,\n } = options;\n\n return async function GET() {\n const urls: SitemapUrl[] = [...staticPages];\n\n // Add dynamic pages\n if (dynamicPages) {\n if (typeof dynamicPages === 'function') {\n const dynamicUrls = await dynamicPages();\n urls.push(...dynamicUrls);\n } else {\n urls.push(...dynamicPages);\n }\n }\n\n // Expand URLs for each locale if i18n is enabled\n let expandedUrls: SitemapUrl[];\n if (i18n) {\n expandedUrls = expandUrlsForLocales(urls, i18n.locales, siteUrl);\n } else {\n expandedUrls = urls.map((url) => ({\n ...url,\n loc: normalizeUrl(url.loc, siteUrl),\n }));\n }\n\n // Generate XML with i18n support\n const sitemap = generateSitemapXml({\n urls: expandedUrls,\n i18n,\n siteUrl,\n });\n\n // Return response\n return new NextResponse(sitemap, {\n status: 200,\n headers: {\n 'Content-Type': 'application/xml',\n 'Cache-Control': cacheControl,\n },\n });\n };\n}\n\n/**\n * Expand URLs to include all locale variants\n * Input: [{ loc: '/page' }] with locales ['en', 'ru']\n * Output: [{ loc: 'https://example.com/en/page' }, { loc: 'https://example.com/ru/page' }]\n */\nfunction expandUrlsForLocales(\n urls: SitemapUrl[],\n locales: string[],\n siteUrl: string\n): SitemapUrl[] {\n const baseSiteUrl = siteUrl.endsWith('/') ? siteUrl.slice(0, -1) : siteUrl;\n const expandedUrls: SitemapUrl[] = [];\n\n for (const url of urls) {\n // Normalize the path (remove leading slash if present for consistency)\n let path = url.loc;\n if (path.startsWith(baseSiteUrl)) {\n path = path.replace(baseSiteUrl, '');\n }\n if (!path.startsWith('/')) {\n path = '/' + path;\n }\n\n // Check if the path already has a locale prefix\n const hasLocalePrefix = locales.some(\n (locale) => path === `/${locale}` || path.startsWith(`/${locale}/`)\n );\n\n if (hasLocalePrefix) {\n // URL already has locale prefix, just normalize it\n expandedUrls.push({\n ...url,\n loc: normalizeUrl(path, siteUrl),\n });\n } else {\n // Create a URL for each locale\n for (const locale of locales) {\n const localePath = path === '/' ? `/${locale}` : `/${locale}${path}`;\n expandedUrls.push({\n ...url,\n loc: `${baseSiteUrl}${localePath}`,\n });\n }\n }\n }\n\n return expandedUrls;\n}\n\n","/**\n * Sitemap Generator\n *\n * Generates XML sitemap from configuration\n * Supports i18n with hreflang tags for multilingual sites\n */\n\nimport type { SitemapUrl } from '../types';\nimport type { SitemapI18nOptions } from './types';\n\nexport interface GenerateSitemapXmlOptions {\n urls: SitemapUrl[];\n i18n?: SitemapI18nOptions;\n siteUrl: string;\n}\n\n/**\n * Generate XML sitemap string from URLs\n * Supports i18n with hreflang alternate links\n */\nexport function generateSitemapXml(\n urlsOrOptions: SitemapUrl[] | GenerateSitemapXmlOptions\n): string {\n // Support both old signature (just urls) and new signature (options object)\n const isOptionsObject = !Array.isArray(urlsOrOptions);\n const urls = isOptionsObject ? urlsOrOptions.urls : urlsOrOptions;\n const i18n = isOptionsObject ? urlsOrOptions.i18n : undefined;\n const siteUrl = isOptionsObject ? urlsOrOptions.siteUrl : '';\n\n // Add xhtml namespace if i18n is enabled\n const namespaces = i18n\n ? `xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\n xmlns:xhtml=\"http://www.w3.org/1999/xhtml\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9\n http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd\"`\n : `xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9\n http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd\"`;\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset ${namespaces}>\n${urls\n .map(({ loc, lastmod, changefreq, priority }) => {\n const hreflangLinks = i18n\n ? generateHreflangLinks(loc, i18n, siteUrl)\n : '';\n\n return ` <url>\n <loc>${escapeXml(loc)}</loc>${hreflangLinks}\n ${lastmod ? `<lastmod>${formatDate(lastmod)}</lastmod>` : ''}\n ${changefreq ? `<changefreq>${changefreq}</changefreq>` : ''}\n ${priority !== undefined ? `<priority>${priority.toFixed(1)}</priority>` : ''}\n </url>`;\n })\n .join('\\n')}\n</urlset>`;\n}\n\n/**\n * Generate hreflang links for a URL\n */\nfunction generateHreflangLinks(\n loc: string,\n i18n: SitemapI18nOptions,\n siteUrl: string\n): string {\n const { locales, defaultLocale } = i18n;\n\n // Extract the path without locale prefix from the URL\n // e.g., https://example.com/en/page -> /page\n const baseSiteUrl = siteUrl.endsWith('/') ? siteUrl.slice(0, -1) : siteUrl;\n let path = loc.replace(baseSiteUrl, '');\n\n // Remove locale prefix if present\n for (const locale of locales) {\n const localePrefix = `/${locale}`;\n if (path === localePrefix || path.startsWith(`${localePrefix}/`)) {\n path = path.slice(localePrefix.length) || '/';\n break;\n }\n }\n\n const links: string[] = [];\n\n // Add hreflang for each locale\n for (const locale of locales) {\n const localePath = path === '/' ? `/${locale}` : `/${locale}${path}`;\n const fullUrl = `${baseSiteUrl}${localePath}`;\n links.push(\n ` <xhtml:link rel=\"alternate\" hreflang=\"${locale}\" href=\"${escapeXml(fullUrl)}\"/>`\n );\n }\n\n // Add x-default pointing to default locale\n const defaultPath = path === '/' ? `/${defaultLocale}` : `/${defaultLocale}${path}`;\n const defaultUrl = `${baseSiteUrl}${defaultPath}`;\n links.push(\n ` <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"${escapeXml(defaultUrl)}\"/>`\n );\n\n return '\\n' + links.join('\\n');\n}\n\n/**\n * Format date for sitemap (ISO 8601)\n */\nfunction formatDate(date: string | Date): string {\n if (typeof date === 'string') {\n return date;\n }\n return date.toISOString().split('T')[0];\n}\n\n/**\n * Escape XML special characters\n */\nfunction escapeXml(unsafe: string): string {\n return unsafe\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Normalize URL (ensure absolute)\n */\nexport function normalizeUrl(url: string, siteUrl: string): string {\n if (url.startsWith('http://') || url.startsWith('https://')) {\n return url;\n }\n const baseUrl = siteUrl.endsWith('/') ? siteUrl.slice(0, -1) : siteUrl;\n const path = url.startsWith('/') ? url : `/${url}`;\n return `${baseUrl}${path}`;\n}\n\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { LocaleCode, I18nTranslations } from '@djangocfg/i18n';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* i18n Types for Next.js App Router
|
|
5
|
+
*
|
|
6
|
+
* Integrates next-intl with @djangocfg/i18n
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
interface I18nConfig {
|
|
10
|
+
/** Supported locales */
|
|
11
|
+
locales: LocaleCode[];
|
|
12
|
+
/** Default locale (fallback) */
|
|
13
|
+
defaultLocale: LocaleCode;
|
|
14
|
+
/** Locale prefix strategy */
|
|
15
|
+
localePrefix?: 'always' | 'as-needed' | 'never';
|
|
16
|
+
/** Cookie name for locale preference */
|
|
17
|
+
cookieName?: string;
|
|
18
|
+
/** Paths to exclude from locale routing (e.g., '/api', '/_next') */
|
|
19
|
+
excludedPaths?: string[];
|
|
20
|
+
}
|
|
21
|
+
interface I18nPluginOptions extends I18nConfig {
|
|
22
|
+
/** Path to i18n request config file (default: './src/i18n/request.ts' or './i18n/request.ts') */
|
|
23
|
+
requestConfig?: string;
|
|
24
|
+
}
|
|
25
|
+
/** Messages structure - compatible with next-intl */
|
|
26
|
+
type Messages = I18nTranslations & {
|
|
27
|
+
[namespace: string]: Record<string, unknown>;
|
|
28
|
+
};
|
|
29
|
+
/** Locale-specific messages loader */
|
|
30
|
+
type MessagesLoader = (locale: LocaleCode) => Promise<Messages> | Messages;
|
|
31
|
+
/** Extension translations that can be merged */
|
|
32
|
+
interface ExtensionMessages {
|
|
33
|
+
namespace: string;
|
|
34
|
+
messages: Record<LocaleCode, Record<string, unknown>>;
|
|
35
|
+
}
|
|
36
|
+
interface I18nProviderProps {
|
|
37
|
+
/** Current locale */
|
|
38
|
+
locale: LocaleCode;
|
|
39
|
+
/** Translation messages */
|
|
40
|
+
messages: Messages;
|
|
41
|
+
/** Time zone for date/time formatting */
|
|
42
|
+
timeZone?: string;
|
|
43
|
+
/** Now value for relative time */
|
|
44
|
+
now?: Date;
|
|
45
|
+
/** Children */
|
|
46
|
+
children: React.ReactNode;
|
|
47
|
+
}
|
|
48
|
+
interface LocaleParams {
|
|
49
|
+
locale: string;
|
|
50
|
+
}
|
|
51
|
+
interface LocaleLayoutProps {
|
|
52
|
+
children: React.ReactNode;
|
|
53
|
+
params: Promise<LocaleParams>;
|
|
54
|
+
}
|
|
55
|
+
interface LocalePageProps {
|
|
56
|
+
params: Promise<LocaleParams>;
|
|
57
|
+
searchParams?: Promise<Record<string, string | string[] | undefined>>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type { ExtensionMessages as E, I18nConfig as I, LocaleParams as L, Messages as M, I18nPluginOptions as a, I18nProviderProps as b, MessagesLoader as c, LocaleLayoutProps as d, LocalePageProps as e };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/nextjs",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.111",
|
|
4
4
|
"description": "Next.js server utilities: sitemap, health, OG images, contact forms, navigation, config",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nextjs",
|
|
@@ -98,6 +98,46 @@
|
|
|
98
98
|
"types": "./dist/pwa/server/routes.d.mts",
|
|
99
99
|
"import": "./dist/pwa/server/routes.mjs",
|
|
100
100
|
"default": "./dist/pwa/server/routes.mjs"
|
|
101
|
+
},
|
|
102
|
+
"./i18n": {
|
|
103
|
+
"types": "./dist/i18n/index.d.mts",
|
|
104
|
+
"import": "./dist/i18n/index.mjs",
|
|
105
|
+
"default": "./dist/i18n/index.mjs"
|
|
106
|
+
},
|
|
107
|
+
"./i18n/server": {
|
|
108
|
+
"types": "./dist/i18n/server.d.mts",
|
|
109
|
+
"import": "./dist/i18n/server.mjs",
|
|
110
|
+
"default": "./dist/i18n/server.mjs"
|
|
111
|
+
},
|
|
112
|
+
"./i18n/client": {
|
|
113
|
+
"types": "./dist/i18n/client.d.mts",
|
|
114
|
+
"import": "./dist/i18n/client.mjs",
|
|
115
|
+
"default": "./dist/i18n/client.mjs"
|
|
116
|
+
},
|
|
117
|
+
"./i18n/proxy": {
|
|
118
|
+
"types": "./dist/i18n/proxy.d.mts",
|
|
119
|
+
"import": "./dist/i18n/proxy.mjs",
|
|
120
|
+
"default": "./dist/i18n/proxy.mjs"
|
|
121
|
+
},
|
|
122
|
+
"./i18n/navigation": {
|
|
123
|
+
"types": "./dist/i18n/navigation.d.mts",
|
|
124
|
+
"import": "./dist/i18n/navigation.mjs",
|
|
125
|
+
"default": "./dist/i18n/navigation.mjs"
|
|
126
|
+
},
|
|
127
|
+
"./i18n/routing": {
|
|
128
|
+
"types": "./dist/i18n/routing.d.mts",
|
|
129
|
+
"import": "./dist/i18n/routing.mjs",
|
|
130
|
+
"default": "./dist/i18n/routing.mjs"
|
|
131
|
+
},
|
|
132
|
+
"./i18n/request": {
|
|
133
|
+
"types": "./dist/i18n/request.d.mts",
|
|
134
|
+
"import": "./dist/i18n/request.mjs",
|
|
135
|
+
"default": "./dist/i18n/request.mjs"
|
|
136
|
+
},
|
|
137
|
+
"./i18n/components": {
|
|
138
|
+
"types": "./dist/i18n/components.d.mts",
|
|
139
|
+
"import": "./dist/i18n/components.mjs",
|
|
140
|
+
"default": "./dist/i18n/components.mjs"
|
|
101
141
|
}
|
|
102
142
|
},
|
|
103
143
|
"files": [
|
|
@@ -120,22 +160,32 @@
|
|
|
120
160
|
"pwa": "tsx src/pwa/cli.ts"
|
|
121
161
|
},
|
|
122
162
|
"peerDependencies": {
|
|
163
|
+
"@djangocfg/i18n": "^2.1.111",
|
|
164
|
+
"@djangocfg/ui-core": "^2.1.111",
|
|
123
165
|
"next": "^16.0.10"
|
|
124
166
|
},
|
|
167
|
+
"peerDependenciesMeta": {
|
|
168
|
+
"@djangocfg/ui-core": {
|
|
169
|
+
"optional": true
|
|
170
|
+
}
|
|
171
|
+
},
|
|
125
172
|
"dependencies": {
|
|
126
173
|
"@serwist/next": "^9.2.3",
|
|
127
174
|
"@serwist/sw": "^9.2.3",
|
|
128
175
|
"chalk": "^5.3.0",
|
|
129
176
|
"conf": "^15.0.2",
|
|
130
177
|
"consola": "^3.4.2",
|
|
178
|
+
"next-intl": "^4.1.0",
|
|
131
179
|
"semver": "^7.7.3",
|
|
132
180
|
"serwist": "^9.2.3",
|
|
133
181
|
"web-push": "^3.6.7"
|
|
134
182
|
},
|
|
135
183
|
"devDependencies": {
|
|
136
|
-
"@djangocfg/
|
|
137
|
-
"@djangocfg/
|
|
138
|
-
"@djangocfg/
|
|
184
|
+
"@djangocfg/i18n": "^2.1.111",
|
|
185
|
+
"@djangocfg/ui-core": "^2.1.111",
|
|
186
|
+
"@djangocfg/imgai": "^2.1.111",
|
|
187
|
+
"@djangocfg/layouts": "^2.1.111",
|
|
188
|
+
"@djangocfg/typescript-config": "^2.1.111",
|
|
139
189
|
"@types/node": "^24.7.2",
|
|
140
190
|
"@types/react": "19.2.2",
|
|
141
191
|
"@types/react-dom": "19.2.1",
|
package/src/config/constants.ts
CHANGED
|
@@ -33,6 +33,7 @@ export const DJANGOCFG_PACKAGES = [
|
|
|
33
33
|
// Default packages to transpile
|
|
34
34
|
// Required for proper RSC (React Server Components) handling
|
|
35
35
|
export const DEFAULT_TRANSPILE_PACKAGES = [
|
|
36
|
+
'@djangocfg/i18n',
|
|
36
37
|
'@djangocfg/ui-core',
|
|
37
38
|
'@djangocfg/ui-nextjs',
|
|
38
39
|
'@djangocfg/layouts',
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
import type { NextConfig } from 'next';
|
|
25
25
|
import type { Configuration as WebpackConfig } from 'webpack';
|
|
26
26
|
|
|
27
|
+
import { type I18nPluginOptions, withI18n } from '../i18n/plugin';
|
|
27
28
|
import { type PWAPluginOptions, withPWA } from '../pwa/plugin';
|
|
28
29
|
import { DEFAULT_OPTIMIZE_PACKAGES, DEFAULT_TRANSPILE_PACKAGES } from './constants';
|
|
29
30
|
import { addCompressionPlugins } from './plugins/compression';
|
|
@@ -63,6 +64,19 @@ export interface BaseNextConfigOptions {
|
|
|
63
64
|
* @default { enabled: true (in production), disable: true (in development) }
|
|
64
65
|
*/
|
|
65
66
|
pwa?: PWAPluginOptions | false;
|
|
67
|
+
/**
|
|
68
|
+
* i18n configuration options for internationalization
|
|
69
|
+
* Enables URL-based locale routing (e.g., /en/about, /ru/about)
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* i18n: {
|
|
74
|
+
* locales: ['en', 'ru', 'ko'],
|
|
75
|
+
* defaultLocale: 'en',
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
i18n?: I18nPluginOptions;
|
|
66
80
|
/** Turbopack configuration (Next.js 16+ default bundler) */
|
|
67
81
|
turbopack?: NextConfig['turbopack'];
|
|
68
82
|
/** Custom webpack configuration function (called after base webpack logic) */
|
|
@@ -282,27 +296,35 @@ export function createBaseNextConfig(
|
|
|
282
296
|
},
|
|
283
297
|
};
|
|
284
298
|
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
+
// Extract plugin options before merge (to avoid conflicts with Next.js config)
|
|
300
|
+
const {
|
|
301
|
+
i18n: i18nOptions,
|
|
302
|
+
pwa: pwaOptions,
|
|
303
|
+
optimizePackageImports,
|
|
304
|
+
isDefaultCfgAdmin,
|
|
305
|
+
checkUpdates,
|
|
306
|
+
autoUpdate,
|
|
307
|
+
forceCheckWorkspace,
|
|
308
|
+
checkPackages,
|
|
309
|
+
autoInstall,
|
|
310
|
+
allowIframeFrom,
|
|
311
|
+
...nextConfigOptions
|
|
312
|
+
} = options;
|
|
313
|
+
|
|
314
|
+
// Deep merge only valid Next.js options with base config
|
|
315
|
+
let finalConfig = deepMerge(baseConfig, nextConfigOptions);
|
|
299
316
|
|
|
300
317
|
// Apply PWA wrapper only if explicitly configured (opt-in)
|
|
301
318
|
// PWA requires sw.ts file in the app, so apps must explicitly enable it
|
|
302
|
-
if (
|
|
303
|
-
finalConfig = withPWA(finalConfig,
|
|
319
|
+
if (pwaOptions) {
|
|
320
|
+
finalConfig = withPWA(finalConfig, pwaOptions);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Apply i18n wrapper if configured
|
|
324
|
+
// Enables next-intl for locale routing and translations
|
|
325
|
+
if (i18nOptions) {
|
|
326
|
+
finalConfig = withI18n(finalConfig, i18nOptions);
|
|
304
327
|
}
|
|
305
|
-
delete (finalConfig as any).pwa;
|
|
306
328
|
|
|
307
329
|
return finalConfig;
|
|
308
330
|
}
|