@comvi/next 0.1.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 +256 -0
- package/dist/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/client/I18nProvider.cjs +101 -0
- package/dist/client/I18nProvider.d.ts +84 -0
- package/dist/client/I18nProvider.d.ts.map +1 -0
- package/dist/client/I18nProvider.js +99 -0
- package/dist/client/index.d.ts +5 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client.cjs +31 -0
- package/dist/client.d.ts +5 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +5 -0
- package/dist/createNextI18n.cjs +153 -0
- package/dist/createNextI18n.d.ts +183 -0
- package/dist/createNextI18n.d.ts.map +1 -0
- package/dist/createNextI18n.js +152 -0
- package/dist/index.cjs +17 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/middleware/createMiddleware.cjs +185 -0
- package/dist/middleware/createMiddleware.d.ts +38 -0
- package/dist/middleware/createMiddleware.d.ts.map +1 -0
- package/dist/middleware/createMiddleware.js +184 -0
- package/dist/middleware/types.d.ts +87 -0
- package/dist/middleware/types.d.ts.map +1 -0
- package/dist/middleware.cjs +3 -0
- package/dist/middleware.d.ts +3 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +2 -0
- package/dist/navigation.cjs +8 -0
- package/dist/navigation.d.ts +5 -0
- package/dist/navigation.d.ts.map +1 -0
- package/dist/navigation.js +5 -0
- package/dist/routing/Link.cjs +42 -0
- package/dist/routing/Link.d.ts +25 -0
- package/dist/routing/Link.d.ts.map +1 -0
- package/dist/routing/Link.js +39 -0
- package/dist/routing/context.cjs +21 -0
- package/dist/routing/context.d.ts +9 -0
- package/dist/routing/context.d.ts.map +1 -0
- package/dist/routing/context.js +18 -0
- package/dist/routing/defineRouting.cjs +141 -0
- package/dist/routing/defineRouting.d.ts +123 -0
- package/dist/routing/defineRouting.d.ts.map +1 -0
- package/dist/routing/defineRouting.js +139 -0
- package/dist/routing/hooks.cjs +104 -0
- package/dist/routing/hooks.d.ts +66 -0
- package/dist/routing/hooks.d.ts.map +1 -0
- package/dist/routing/hooks.js +102 -0
- package/dist/routing/types.d.ts +35 -0
- package/dist/routing/types.d.ts.map +1 -0
- package/dist/routing/utils.cjs +94 -0
- package/dist/routing/utils.d.ts +8 -0
- package/dist/routing/utils.d.ts.map +1 -0
- package/dist/routing/utils.js +91 -0
- package/dist/routing.cjs +5 -0
- package/dist/routing.d.ts +4 -0
- package/dist/routing.d.ts.map +1 -0
- package/dist/routing.js +2 -0
- package/dist/server/cache.cjs +69 -0
- package/dist/server/cache.d.ts +42 -0
- package/dist/server/cache.d.ts.map +1 -0
- package/dist/server/cache.js +66 -0
- package/dist/server/ensureInitialized.cjs +19 -0
- package/dist/server/ensureInitialized.d.ts +7 -0
- package/dist/server/ensureInitialized.d.ts.map +1 -0
- package/dist/server/ensureInitialized.js +19 -0
- package/dist/server/getI18n.cjs +115 -0
- package/dist/server/getI18n.d.ts +61 -0
- package/dist/server/getI18n.d.ts.map +1 -0
- package/dist/server/getI18n.js +114 -0
- package/dist/server/getLocale.cjs +37 -0
- package/dist/server/getLocale.d.ts +22 -0
- package/dist/server/getLocale.d.ts.map +1 -0
- package/dist/server/getLocale.js +36 -0
- package/dist/server/loadTranslations.cjs +54 -0
- package/dist/server/loadTranslations.d.ts +34 -0
- package/dist/server/loadTranslations.d.ts.map +1 -0
- package/dist/server/loadTranslations.js +54 -0
- package/dist/server/setRequestLocale.cjs +31 -0
- package/dist/server/setRequestLocale.d.ts +26 -0
- package/dist/server/setRequestLocale.d.ts.map +1 -0
- package/dist/server/setRequestLocale.js +31 -0
- package/dist/server/types.d.ts +43 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server.cjs +11 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +6 -0
- package/package.json +111 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
|
|
3
|
+
const require_defineRouting = require("./defineRouting.cjs");
|
|
4
|
+
let react = require("react");
|
|
5
|
+
react = require_runtime.__toESM(react);
|
|
6
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
7
|
+
//#region src/routing/context.tsx
|
|
8
|
+
var RoutingContext = (0, react.createContext)(null);
|
|
9
|
+
function RoutingProvider({ routing, children }) {
|
|
10
|
+
const value = (0, react.useMemo)(() => require_defineRouting.defineRouting(routing), [routing]);
|
|
11
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoutingContext.Provider, {
|
|
12
|
+
value,
|
|
13
|
+
children
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function useRoutingConfig() {
|
|
17
|
+
return (0, react.useContext)(RoutingContext);
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
exports.RoutingProvider = RoutingProvider;
|
|
21
|
+
exports.useRoutingConfig = useRoutingConfig;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { RoutingConfig } from './types';
|
|
3
|
+
export interface RoutingProviderProps {
|
|
4
|
+
routing: RoutingConfig;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
}
|
|
7
|
+
export declare function RoutingProvider({ routing, children }: RoutingProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export declare function useRoutingConfig(): Required<RoutingConfig> | null;
|
|
9
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/routing/context.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA6C,MAAM,OAAO,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAK7C,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,eAAe,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,oBAAoB,2CAG1E;AAED,wBAAgB,gBAAgB,IAAI,QAAQ,CAAC,aAAa,CAAC,GAAG,IAAI,CAEjE"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { defineRouting } from "./defineRouting.js";
|
|
3
|
+
import { createContext, useContext, useMemo } from "react";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
//#region src/routing/context.tsx
|
|
6
|
+
var RoutingContext = createContext(null);
|
|
7
|
+
function RoutingProvider({ routing, children }) {
|
|
8
|
+
const value = useMemo(() => defineRouting(routing), [routing]);
|
|
9
|
+
return /* @__PURE__ */ jsx(RoutingContext.Provider, {
|
|
10
|
+
value,
|
|
11
|
+
children
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function useRoutingConfig() {
|
|
15
|
+
return useContext(RoutingContext);
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { RoutingProvider, useRoutingConfig };
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
//#region src/routing/defineRouting.ts
|
|
2
|
+
/**
|
|
3
|
+
* Define routing configuration for your app
|
|
4
|
+
*
|
|
5
|
+
* This configuration is used by both the middleware and navigation components
|
|
6
|
+
* to handle localized routing.
|
|
7
|
+
*
|
|
8
|
+
* @param config - Routing configuration
|
|
9
|
+
* @returns The same configuration with defaults applied
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* // i18n/routing.ts
|
|
14
|
+
* import { defineRouting } from '@comvi/next/routing';
|
|
15
|
+
*
|
|
16
|
+
* export const routing = defineRouting({
|
|
17
|
+
* locales: ['en', 'uk', 'de'],
|
|
18
|
+
* defaultLocale: 'en',
|
|
19
|
+
* localePrefix: 'as-needed',
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
function defineRouting(config) {
|
|
24
|
+
return {
|
|
25
|
+
...config,
|
|
26
|
+
localePrefix: config.localePrefix ?? "as-needed",
|
|
27
|
+
localeCookie: config.localeCookie ?? "NEXT_LOCALE",
|
|
28
|
+
pathnames: config.pathnames ?? {}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Type guard to check if a string is a valid locale
|
|
33
|
+
*
|
|
34
|
+
* Use this in layouts/pages to validate the locale parameter
|
|
35
|
+
* and get proper TypeScript narrowing.
|
|
36
|
+
*
|
|
37
|
+
* @param locales - Array of supported locales
|
|
38
|
+
* @param locale - String to check
|
|
39
|
+
* @returns True if locale is valid
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* import { hasLocale } from '@comvi/next/routing';
|
|
44
|
+
* import { routing } from '@/i18n/routing';
|
|
45
|
+
* import { notFound } from 'next/navigation';
|
|
46
|
+
*
|
|
47
|
+
* export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
|
|
48
|
+
* const { locale } = await params;
|
|
49
|
+
*
|
|
50
|
+
* if (!hasLocale(routing.locales, locale)) {
|
|
51
|
+
* notFound();
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* // locale is now typed as 'en' | 'uk' | 'de'
|
|
55
|
+
* return <div>Locale: {locale}</div>;
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
function hasLocale(locales, locale) {
|
|
60
|
+
return locales.includes(locale);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Creates a getPathname function bound to your routing configuration
|
|
64
|
+
*
|
|
65
|
+
* Use this to construct localized pathnames for sitemaps, hreflang tags,
|
|
66
|
+
* and canonical URLs.
|
|
67
|
+
*
|
|
68
|
+
* @param config - Your routing configuration
|
|
69
|
+
* @returns A getPathname function
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* // i18n/navigation.ts
|
|
74
|
+
* import { createGetPathname } from '@comvi/next/routing';
|
|
75
|
+
* import { routing } from './routing';
|
|
76
|
+
*
|
|
77
|
+
* export const getPathname = createGetPathname(routing);
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* // app/sitemap.ts
|
|
83
|
+
* import { getPathname } from '@/i18n/navigation';
|
|
84
|
+
* import { routing } from '@/i18n/routing';
|
|
85
|
+
*
|
|
86
|
+
* export default function sitemap() {
|
|
87
|
+
* const baseUrl = 'https://example.com';
|
|
88
|
+
* const pages = ['/', '/about', '/contact'];
|
|
89
|
+
*
|
|
90
|
+
* return pages.flatMap((page) =>
|
|
91
|
+
* routing.locales.map((locale) => ({
|
|
92
|
+
* url: `${baseUrl}${getPathname({ locale, href: page })}`,
|
|
93
|
+
* lastModified: new Date(),
|
|
94
|
+
* alternates: {
|
|
95
|
+
* languages: Object.fromEntries(
|
|
96
|
+
* routing.locales.map((l) => [
|
|
97
|
+
* l,
|
|
98
|
+
* `${baseUrl}${getPathname({ locale: l, href: page })}`,
|
|
99
|
+
* ])
|
|
100
|
+
* ),
|
|
101
|
+
* },
|
|
102
|
+
* }))
|
|
103
|
+
* );
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* // Generate hreflang tags
|
|
110
|
+
* import { getPathname } from '@/i18n/navigation';
|
|
111
|
+
* import { routing } from '@/i18n/routing';
|
|
112
|
+
*
|
|
113
|
+
* function generateHreflangTags(currentPath: string) {
|
|
114
|
+
* return routing.locales.map((locale) => ({
|
|
115
|
+
* rel: 'alternate',
|
|
116
|
+
* hrefLang: locale,
|
|
117
|
+
* href: `https://example.com${getPathname({ locale, href: currentPath })}`,
|
|
118
|
+
* }));
|
|
119
|
+
* }
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
function createGetPathname(config) {
|
|
123
|
+
const { locales, defaultLocale, localePrefix, pathnames } = config;
|
|
124
|
+
return function getPathname({ locale, href }) {
|
|
125
|
+
if (!locales.includes(locale)) console.warn(`[getPathname] Unknown locale "${locale}". Expected one of: ${locales.join(", ")}`);
|
|
126
|
+
const localizedPath = pathnames[href]?.[locale] ?? href;
|
|
127
|
+
const normalizedPath = localizedPath.startsWith("/") ? localizedPath : `/${localizedPath}`;
|
|
128
|
+
switch (localePrefix) {
|
|
129
|
+
case "always": return `/${locale}${normalizedPath === "/" ? "" : normalizedPath}`;
|
|
130
|
+
case "as-needed":
|
|
131
|
+
if (locale === defaultLocale) return normalizedPath;
|
|
132
|
+
return `/${locale}${normalizedPath === "/" ? "" : normalizedPath}`;
|
|
133
|
+
case "never": return normalizedPath;
|
|
134
|
+
default: return normalizedPath;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
exports.createGetPathname = createGetPathname;
|
|
140
|
+
exports.defineRouting = defineRouting;
|
|
141
|
+
exports.hasLocale = hasLocale;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { RoutingConfig } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Define routing configuration for your app
|
|
4
|
+
*
|
|
5
|
+
* This configuration is used by both the middleware and navigation components
|
|
6
|
+
* to handle localized routing.
|
|
7
|
+
*
|
|
8
|
+
* @param config - Routing configuration
|
|
9
|
+
* @returns The same configuration with defaults applied
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* // i18n/routing.ts
|
|
14
|
+
* import { defineRouting } from '@comvi/next/routing';
|
|
15
|
+
*
|
|
16
|
+
* export const routing = defineRouting({
|
|
17
|
+
* locales: ['en', 'uk', 'de'],
|
|
18
|
+
* defaultLocale: 'en',
|
|
19
|
+
* localePrefix: 'as-needed',
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function defineRouting<T extends string>(config: RoutingConfig<T>): Required<RoutingConfig<T>>;
|
|
24
|
+
/**
|
|
25
|
+
* Type guard to check if a string is a valid locale
|
|
26
|
+
*
|
|
27
|
+
* Use this in layouts/pages to validate the locale parameter
|
|
28
|
+
* and get proper TypeScript narrowing.
|
|
29
|
+
*
|
|
30
|
+
* @param locales - Array of supported locales
|
|
31
|
+
* @param locale - String to check
|
|
32
|
+
* @returns True if locale is valid
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* import { hasLocale } from '@comvi/next/routing';
|
|
37
|
+
* import { routing } from '@/i18n/routing';
|
|
38
|
+
* import { notFound } from 'next/navigation';
|
|
39
|
+
*
|
|
40
|
+
* export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
|
|
41
|
+
* const { locale } = await params;
|
|
42
|
+
*
|
|
43
|
+
* if (!hasLocale(routing.locales, locale)) {
|
|
44
|
+
* notFound();
|
|
45
|
+
* }
|
|
46
|
+
*
|
|
47
|
+
* // locale is now typed as 'en' | 'uk' | 'de'
|
|
48
|
+
* return <div>Locale: {locale}</div>;
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function hasLocale<T extends string>(locales: readonly T[], locale: string): locale is T;
|
|
53
|
+
/**
|
|
54
|
+
* Options for getPathname function
|
|
55
|
+
*/
|
|
56
|
+
export interface GetPathnameOptions {
|
|
57
|
+
/** Target locale */
|
|
58
|
+
locale: string;
|
|
59
|
+
/** The pathname (without locale prefix) */
|
|
60
|
+
href: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Creates a getPathname function bound to your routing configuration
|
|
64
|
+
*
|
|
65
|
+
* Use this to construct localized pathnames for sitemaps, hreflang tags,
|
|
66
|
+
* and canonical URLs.
|
|
67
|
+
*
|
|
68
|
+
* @param config - Your routing configuration
|
|
69
|
+
* @returns A getPathname function
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* // i18n/navigation.ts
|
|
74
|
+
* import { createGetPathname } from '@comvi/next/routing';
|
|
75
|
+
* import { routing } from './routing';
|
|
76
|
+
*
|
|
77
|
+
* export const getPathname = createGetPathname(routing);
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* // app/sitemap.ts
|
|
83
|
+
* import { getPathname } from '@/i18n/navigation';
|
|
84
|
+
* import { routing } from '@/i18n/routing';
|
|
85
|
+
*
|
|
86
|
+
* export default function sitemap() {
|
|
87
|
+
* const baseUrl = 'https://example.com';
|
|
88
|
+
* const pages = ['/', '/about', '/contact'];
|
|
89
|
+
*
|
|
90
|
+
* return pages.flatMap((page) =>
|
|
91
|
+
* routing.locales.map((locale) => ({
|
|
92
|
+
* url: `${baseUrl}${getPathname({ locale, href: page })}`,
|
|
93
|
+
* lastModified: new Date(),
|
|
94
|
+
* alternates: {
|
|
95
|
+
* languages: Object.fromEntries(
|
|
96
|
+
* routing.locales.map((l) => [
|
|
97
|
+
* l,
|
|
98
|
+
* `${baseUrl}${getPathname({ locale: l, href: page })}`,
|
|
99
|
+
* ])
|
|
100
|
+
* ),
|
|
101
|
+
* },
|
|
102
|
+
* }))
|
|
103
|
+
* );
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* // Generate hreflang tags
|
|
110
|
+
* import { getPathname } from '@/i18n/navigation';
|
|
111
|
+
* import { routing } from '@/i18n/routing';
|
|
112
|
+
*
|
|
113
|
+
* function generateHreflangTags(currentPath: string) {
|
|
114
|
+
* return routing.locales.map((locale) => ({
|
|
115
|
+
* rel: 'alternate',
|
|
116
|
+
* hrefLang: locale,
|
|
117
|
+
* href: `https://example.com${getPathname({ locale, href: currentPath })}`,
|
|
118
|
+
* }));
|
|
119
|
+
* }
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export declare function createGetPathname<T extends string>(config: Required<RoutingConfig<T>>): (options: GetPathnameOptions) => string;
|
|
123
|
+
//# sourceMappingURL=defineRouting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defineRouting.d.ts","sourceRoot":"","sources":["../../src/routing/defineRouting.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAC5C,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,GACvB,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAO5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,CAAC,CAE9F;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,oBAAoB;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,EAChD,MAAM,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,GACjC,CAAC,OAAO,EAAE,kBAAkB,KAAK,MAAM,CAsCzC"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
//#region src/routing/defineRouting.ts
|
|
2
|
+
/**
|
|
3
|
+
* Define routing configuration for your app
|
|
4
|
+
*
|
|
5
|
+
* This configuration is used by both the middleware and navigation components
|
|
6
|
+
* to handle localized routing.
|
|
7
|
+
*
|
|
8
|
+
* @param config - Routing configuration
|
|
9
|
+
* @returns The same configuration with defaults applied
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* // i18n/routing.ts
|
|
14
|
+
* import { defineRouting } from '@comvi/next/routing';
|
|
15
|
+
*
|
|
16
|
+
* export const routing = defineRouting({
|
|
17
|
+
* locales: ['en', 'uk', 'de'],
|
|
18
|
+
* defaultLocale: 'en',
|
|
19
|
+
* localePrefix: 'as-needed',
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
function defineRouting(config) {
|
|
24
|
+
return {
|
|
25
|
+
...config,
|
|
26
|
+
localePrefix: config.localePrefix ?? "as-needed",
|
|
27
|
+
localeCookie: config.localeCookie ?? "NEXT_LOCALE",
|
|
28
|
+
pathnames: config.pathnames ?? {}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Type guard to check if a string is a valid locale
|
|
33
|
+
*
|
|
34
|
+
* Use this in layouts/pages to validate the locale parameter
|
|
35
|
+
* and get proper TypeScript narrowing.
|
|
36
|
+
*
|
|
37
|
+
* @param locales - Array of supported locales
|
|
38
|
+
* @param locale - String to check
|
|
39
|
+
* @returns True if locale is valid
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* import { hasLocale } from '@comvi/next/routing';
|
|
44
|
+
* import { routing } from '@/i18n/routing';
|
|
45
|
+
* import { notFound } from 'next/navigation';
|
|
46
|
+
*
|
|
47
|
+
* export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
|
|
48
|
+
* const { locale } = await params;
|
|
49
|
+
*
|
|
50
|
+
* if (!hasLocale(routing.locales, locale)) {
|
|
51
|
+
* notFound();
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* // locale is now typed as 'en' | 'uk' | 'de'
|
|
55
|
+
* return <div>Locale: {locale}</div>;
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
function hasLocale(locales, locale) {
|
|
60
|
+
return locales.includes(locale);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Creates a getPathname function bound to your routing configuration
|
|
64
|
+
*
|
|
65
|
+
* Use this to construct localized pathnames for sitemaps, hreflang tags,
|
|
66
|
+
* and canonical URLs.
|
|
67
|
+
*
|
|
68
|
+
* @param config - Your routing configuration
|
|
69
|
+
* @returns A getPathname function
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* // i18n/navigation.ts
|
|
74
|
+
* import { createGetPathname } from '@comvi/next/routing';
|
|
75
|
+
* import { routing } from './routing';
|
|
76
|
+
*
|
|
77
|
+
* export const getPathname = createGetPathname(routing);
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* // app/sitemap.ts
|
|
83
|
+
* import { getPathname } from '@/i18n/navigation';
|
|
84
|
+
* import { routing } from '@/i18n/routing';
|
|
85
|
+
*
|
|
86
|
+
* export default function sitemap() {
|
|
87
|
+
* const baseUrl = 'https://example.com';
|
|
88
|
+
* const pages = ['/', '/about', '/contact'];
|
|
89
|
+
*
|
|
90
|
+
* return pages.flatMap((page) =>
|
|
91
|
+
* routing.locales.map((locale) => ({
|
|
92
|
+
* url: `${baseUrl}${getPathname({ locale, href: page })}`,
|
|
93
|
+
* lastModified: new Date(),
|
|
94
|
+
* alternates: {
|
|
95
|
+
* languages: Object.fromEntries(
|
|
96
|
+
* routing.locales.map((l) => [
|
|
97
|
+
* l,
|
|
98
|
+
* `${baseUrl}${getPathname({ locale: l, href: page })}`,
|
|
99
|
+
* ])
|
|
100
|
+
* ),
|
|
101
|
+
* },
|
|
102
|
+
* }))
|
|
103
|
+
* );
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* // Generate hreflang tags
|
|
110
|
+
* import { getPathname } from '@/i18n/navigation';
|
|
111
|
+
* import { routing } from '@/i18n/routing';
|
|
112
|
+
*
|
|
113
|
+
* function generateHreflangTags(currentPath: string) {
|
|
114
|
+
* return routing.locales.map((locale) => ({
|
|
115
|
+
* rel: 'alternate',
|
|
116
|
+
* hrefLang: locale,
|
|
117
|
+
* href: `https://example.com${getPathname({ locale, href: currentPath })}`,
|
|
118
|
+
* }));
|
|
119
|
+
* }
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
function createGetPathname(config) {
|
|
123
|
+
const { locales, defaultLocale, localePrefix, pathnames } = config;
|
|
124
|
+
return function getPathname({ locale, href }) {
|
|
125
|
+
if (!locales.includes(locale)) console.warn(`[getPathname] Unknown locale "${locale}". Expected one of: ${locales.join(", ")}`);
|
|
126
|
+
const localizedPath = pathnames[href]?.[locale] ?? href;
|
|
127
|
+
const normalizedPath = localizedPath.startsWith("/") ? localizedPath : `/${localizedPath}`;
|
|
128
|
+
switch (localePrefix) {
|
|
129
|
+
case "always": return `/${locale}${normalizedPath === "/" ? "" : normalizedPath}`;
|
|
130
|
+
case "as-needed":
|
|
131
|
+
if (locale === defaultLocale) return normalizedPath;
|
|
132
|
+
return `/${locale}${normalizedPath === "/" ? "" : normalizedPath}`;
|
|
133
|
+
case "never": return normalizedPath;
|
|
134
|
+
default: return normalizedPath;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
export { createGetPathname, defineRouting, hasLocale };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
require("../_virtual/_rolldown/runtime.cjs");
|
|
4
|
+
const require_context = require("./context.cjs");
|
|
5
|
+
const require_utils = require("./utils.cjs");
|
|
6
|
+
let react = require("react");
|
|
7
|
+
let _comvi_react = require("@comvi/react");
|
|
8
|
+
let next_navigation = require("next/navigation");
|
|
9
|
+
//#region src/routing/hooks.ts
|
|
10
|
+
/**
|
|
11
|
+
* Get pathname without locale prefix
|
|
12
|
+
*
|
|
13
|
+
* This hook returns the current pathname with the locale prefix removed,
|
|
14
|
+
* making it easier to work with routes in a locale-agnostic way.
|
|
15
|
+
*
|
|
16
|
+
* @returns Pathname without locale prefix
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* import { usePathname } from '@comvi/next/navigation';
|
|
21
|
+
*
|
|
22
|
+
* function MyComponent() {
|
|
23
|
+
* const pathname = usePathname();
|
|
24
|
+
* // If URL is /en/about, pathname is /about
|
|
25
|
+
* return <p>Current page: {pathname}</p>;
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
function usePathname() {
|
|
30
|
+
const pathname = (0, next_navigation.usePathname)() ?? "/";
|
|
31
|
+
const routing = require_context.useRoutingConfig();
|
|
32
|
+
const { locale } = (0, _comvi_react.useI18n)();
|
|
33
|
+
if (routing) return require_utils.getCanonicalPathname(require_utils.stripLocalePrefix(pathname, routing.locales), routing, locale);
|
|
34
|
+
if (pathname.startsWith(`/${locale}/`)) return pathname.slice(locale.length + 1);
|
|
35
|
+
if (pathname === `/${locale}`) return "/";
|
|
36
|
+
return pathname;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Localized router with automatic locale prefixing
|
|
40
|
+
*
|
|
41
|
+
* This hook wraps Next.js useRouter and automatically adds locale
|
|
42
|
+
* prefixes to navigation methods.
|
|
43
|
+
*
|
|
44
|
+
* @returns Localized router object
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```tsx
|
|
48
|
+
* import { useLocalizedRouter } from '@comvi/next/navigation';
|
|
49
|
+
*
|
|
50
|
+
* function MyComponent() {
|
|
51
|
+
* const router = useLocalizedRouter();
|
|
52
|
+
*
|
|
53
|
+
* const handleClick = () => {
|
|
54
|
+
* router.push('/about'); // Navigates to /en/about (or current locale)
|
|
55
|
+
* };
|
|
56
|
+
*
|
|
57
|
+
* const handleGerman = () => {
|
|
58
|
+
* router.push('/about', 'de'); // Navigates to /de/about
|
|
59
|
+
* };
|
|
60
|
+
*
|
|
61
|
+
* return <button onClick={handleClick}>Go to About</button>;
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
function useLocalizedRouter() {
|
|
66
|
+
const router = (0, next_navigation.useRouter)();
|
|
67
|
+
const { locale } = (0, _comvi_react.useI18n)();
|
|
68
|
+
const routing = require_context.useRoutingConfig();
|
|
69
|
+
const push = (0, react.useCallback)((href, targetLocale) => {
|
|
70
|
+
const localizedHref = require_utils.localizeHref(href, targetLocale ?? locale, routing ?? void 0);
|
|
71
|
+
router.push(localizedHref);
|
|
72
|
+
}, [
|
|
73
|
+
router,
|
|
74
|
+
locale,
|
|
75
|
+
routing
|
|
76
|
+
]);
|
|
77
|
+
const replace = (0, react.useCallback)((href, targetLocale) => {
|
|
78
|
+
const localizedHref = require_utils.localizeHref(href, targetLocale ?? locale, routing ?? void 0);
|
|
79
|
+
router.replace(localizedHref);
|
|
80
|
+
}, [
|
|
81
|
+
router,
|
|
82
|
+
locale,
|
|
83
|
+
routing
|
|
84
|
+
]);
|
|
85
|
+
const prefetch = (0, react.useCallback)((href, targetLocale) => {
|
|
86
|
+
const localizedHref = require_utils.localizeHref(href, targetLocale ?? locale, routing ?? void 0);
|
|
87
|
+
router.prefetch(localizedHref);
|
|
88
|
+
}, [
|
|
89
|
+
router,
|
|
90
|
+
locale,
|
|
91
|
+
routing
|
|
92
|
+
]);
|
|
93
|
+
return {
|
|
94
|
+
push,
|
|
95
|
+
replace,
|
|
96
|
+
back: router.back,
|
|
97
|
+
forward: router.forward,
|
|
98
|
+
refresh: router.refresh,
|
|
99
|
+
prefetch
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
exports.useLocalizedRouter = useLocalizedRouter;
|
|
104
|
+
exports.usePathname = usePathname;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get pathname without locale prefix
|
|
3
|
+
*
|
|
4
|
+
* This hook returns the current pathname with the locale prefix removed,
|
|
5
|
+
* making it easier to work with routes in a locale-agnostic way.
|
|
6
|
+
*
|
|
7
|
+
* @returns Pathname without locale prefix
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* import { usePathname } from '@comvi/next/navigation';
|
|
12
|
+
*
|
|
13
|
+
* function MyComponent() {
|
|
14
|
+
* const pathname = usePathname();
|
|
15
|
+
* // If URL is /en/about, pathname is /about
|
|
16
|
+
* return <p>Current page: {pathname}</p>;
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function usePathname(): string;
|
|
21
|
+
/**
|
|
22
|
+
* Return type for useLocalizedRouter
|
|
23
|
+
*/
|
|
24
|
+
export interface LocalizedRouter {
|
|
25
|
+
/** Navigate to a localized path */
|
|
26
|
+
push: (href: string, locale?: string) => void;
|
|
27
|
+
/** Replace current history entry with a localized path */
|
|
28
|
+
replace: (href: string, locale?: string) => void;
|
|
29
|
+
/** Navigate back */
|
|
30
|
+
back: () => void;
|
|
31
|
+
/** Navigate forward */
|
|
32
|
+
forward: () => void;
|
|
33
|
+
/** Refresh the current page */
|
|
34
|
+
refresh: () => void;
|
|
35
|
+
/** Prefetch a localized path */
|
|
36
|
+
prefetch: (href: string, locale?: string) => void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Localized router with automatic locale prefixing
|
|
40
|
+
*
|
|
41
|
+
* This hook wraps Next.js useRouter and automatically adds locale
|
|
42
|
+
* prefixes to navigation methods.
|
|
43
|
+
*
|
|
44
|
+
* @returns Localized router object
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```tsx
|
|
48
|
+
* import { useLocalizedRouter } from '@comvi/next/navigation';
|
|
49
|
+
*
|
|
50
|
+
* function MyComponent() {
|
|
51
|
+
* const router = useLocalizedRouter();
|
|
52
|
+
*
|
|
53
|
+
* const handleClick = () => {
|
|
54
|
+
* router.push('/about'); // Navigates to /en/about (or current locale)
|
|
55
|
+
* };
|
|
56
|
+
*
|
|
57
|
+
* const handleGerman = () => {
|
|
58
|
+
* router.push('/about', 'de'); // Navigates to /de/about
|
|
59
|
+
* };
|
|
60
|
+
*
|
|
61
|
+
* return <button onClick={handleClick}>Go to About</button>;
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare function useLocalizedRouter(): LocalizedRouter;
|
|
66
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/routing/hooks.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAkBpC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,mCAAmC;IACnC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,0DAA0D;IAC1D,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,oBAAoB;IACpB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,uBAAuB;IACvB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,+BAA+B;IAC/B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gCAAgC;IAChC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACnD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAwCpD"}
|