@better-translate/nextjs 1.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jorge Alvarenga
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @better-translate/nextjs
2
+
3
+ `@better-translate/nextjs` adds locale routing, request helpers, navigation helpers, and proxy support for Next.js App Router. Use it with `@better-translate/core`, and add `@better-translate/react` only if you need client-side hooks.
4
+
5
+ Full docs: [better-translate-placeholder.com/en/docs/adapters/nextjs](https://better-translate-placeholder.com/en/docs/adapters/nextjs)
@@ -0,0 +1,291 @@
1
+ // src/shared.ts
2
+ var DEFAULT_ROUTE_TEMPLATE = "/[lang]";
3
+ function hasLocale(locales, value) {
4
+ return locales.includes(value);
5
+ }
6
+ function defineRouting(config) {
7
+ const routeTemplate = config.routeTemplate ?? DEFAULT_ROUTE_TEMPLATE;
8
+ validateRouting({
9
+ ...config,
10
+ routeTemplate
11
+ });
12
+ const domains = config.domains?.map(
13
+ (domain) => Object.freeze({
14
+ ...domain,
15
+ locales: domain.locales ? [...domain.locales] : void 0
16
+ })
17
+ );
18
+ return Object.freeze({
19
+ locales: [...config.locales],
20
+ defaultLocale: config.defaultLocale,
21
+ domains,
22
+ routeTemplate
23
+ });
24
+ }
25
+ function normalizePathname(pathname) {
26
+ if (!pathname) {
27
+ return "/";
28
+ }
29
+ return pathname.startsWith("/") ? pathname : `/${pathname}`;
30
+ }
31
+ function getPathnameLocale(pathname, routing) {
32
+ const parsedTemplate = parseRouteTemplate(routing.routeTemplate);
33
+ const pathnameSegments = splitPathname(pathname);
34
+ if (pathnameSegments.length < parsedTemplate.localizedSegments.length) {
35
+ return void 0;
36
+ }
37
+ for (let index = 0; index < parsedTemplate.localizedSegments.length; index += 1) {
38
+ const templateSegment = parsedTemplate.localizedSegments[index];
39
+ const pathnameSegment = pathnameSegments[index];
40
+ if (index === parsedTemplate.localeSegmentIndex) {
41
+ if (!pathnameSegment || !hasLocale(routing.locales, pathnameSegment)) {
42
+ return void 0;
43
+ }
44
+ continue;
45
+ }
46
+ if (pathnameSegment !== templateSegment) {
47
+ return void 0;
48
+ }
49
+ }
50
+ return pathnameSegments[parsedTemplate.localeSegmentIndex];
51
+ }
52
+ function isPathnameInScope(pathname, routing) {
53
+ if (getPathnameLocale(pathname, routing)) {
54
+ return true;
55
+ }
56
+ const parsedTemplate = parseRouteTemplate(routing.routeTemplate);
57
+ const pathnameSegments = splitPathname(pathname);
58
+ if (pathnameSegments.length < parsedTemplate.deLocalizedSegments.length) {
59
+ return false;
60
+ }
61
+ for (let index = 0; index < parsedTemplate.deLocalizedSegments.length; index += 1) {
62
+ if (pathnameSegments[index] !== parsedTemplate.deLocalizedSegments[index]) {
63
+ return false;
64
+ }
65
+ }
66
+ return true;
67
+ }
68
+ function stripLocaleFromPathname(pathname, routing) {
69
+ const locale = getPathnameLocale(pathname, routing);
70
+ if (!locale) {
71
+ return normalizePathname(pathname);
72
+ }
73
+ const parsedTemplate = parseRouteTemplate(routing.routeTemplate);
74
+ const pathnameSegments = splitPathname(pathname);
75
+ pathnameSegments.splice(parsedTemplate.localeSegmentIndex, 1);
76
+ return joinPathname(pathnameSegments);
77
+ }
78
+ function localizePathname(pathname, locale, routing) {
79
+ const normalizedPathname = normalizePathname(pathname);
80
+ const deLocalizedPathname = stripLocaleFromPathname(
81
+ normalizedPathname,
82
+ routing
83
+ );
84
+ if (!isPathnameInScope(deLocalizedPathname, routing)) {
85
+ return normalizedPathname;
86
+ }
87
+ const parsedTemplate = parseRouteTemplate(routing.routeTemplate);
88
+ const pathnameSegments = splitPathname(deLocalizedPathname);
89
+ const localizedSegments = [...pathnameSegments];
90
+ localizedSegments.splice(parsedTemplate.localeSegmentIndex, 0, locale);
91
+ return joinPathname(localizedSegments);
92
+ }
93
+ function splitHrefString(href) {
94
+ const hashIndex = href.indexOf("#");
95
+ const searchIndex = href.indexOf("?");
96
+ const pathnameEnd = hashIndex === -1 ? searchIndex === -1 ? href.length : searchIndex : searchIndex === -1 ? hashIndex : Math.min(searchIndex, hashIndex);
97
+ const pathname = href.slice(0, pathnameEnd) || "/";
98
+ const search = searchIndex === -1 ? "" : href.slice(searchIndex, hashIndex === -1 ? href.length : hashIndex);
99
+ const hash = hashIndex === -1 ? "" : href.slice(hashIndex);
100
+ return {
101
+ pathname: normalizePathname(pathname),
102
+ search,
103
+ hash
104
+ };
105
+ }
106
+ function isAbsoluteHref(href) {
107
+ return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(href) || href.startsWith("//");
108
+ }
109
+ function normalizeHost(host) {
110
+ return host.toLowerCase().replace(/:\d+$/, "");
111
+ }
112
+ function getDomainForLocale(routing, locale) {
113
+ return routing.domains?.find(
114
+ (domain) => getDomainLocales(domain).includes(locale)
115
+ );
116
+ }
117
+ function getLocaleFromDomain(routing, host) {
118
+ const normalizedHost = normalizeHost(host);
119
+ return routing.domains?.find(
120
+ (domain) => normalizeHost(domain.domain) === normalizedHost
121
+ )?.defaultLocale;
122
+ }
123
+ function buildDomainAwareHref(routing, href, locale, options) {
124
+ if (isAbsoluteHref(href)) {
125
+ return {
126
+ href,
127
+ external: true
128
+ };
129
+ }
130
+ const { hash, pathname, search } = splitHrefString(href);
131
+ const isScopedPath = isPathnameInScope(pathname, routing);
132
+ if (!isScopedPath) {
133
+ return {
134
+ href: `${pathname}${search}${hash}`,
135
+ external: false
136
+ };
137
+ }
138
+ const localizedPathname = localizePathname(pathname, locale, routing);
139
+ const localizedHref = `${localizedPathname}${search}${hash}`;
140
+ const targetDomain = getDomainForLocale(routing, locale);
141
+ if (!targetDomain) {
142
+ return {
143
+ href: localizedHref,
144
+ external: false
145
+ };
146
+ }
147
+ const targetHost = normalizeHost(targetDomain.domain);
148
+ const currentHost = options?.currentHost ? normalizeHost(options.currentHost) : void 0;
149
+ const shouldUseAbsolute = options?.forceAbsolute === true || currentHost === void 0 || currentHost !== targetHost;
150
+ if (!shouldUseAbsolute) {
151
+ return {
152
+ href: localizedHref,
153
+ external: false
154
+ };
155
+ }
156
+ const protocol = targetDomain.protocol ?? options?.protocol ?? "https";
157
+ return {
158
+ href: `${protocol}://${targetHost}${localizedHref}`,
159
+ external: currentHost === void 0 ? true : currentHost !== targetHost
160
+ };
161
+ }
162
+ function parseRouteTemplate(routeTemplate) {
163
+ const normalizedRouteTemplate = normalizePathname(
164
+ routeTemplate ?? DEFAULT_ROUTE_TEMPLATE
165
+ );
166
+ const localizedSegments = splitPathname(normalizedRouteTemplate);
167
+ let localeSegmentIndex = -1;
168
+ let localeParamName = "";
169
+ for (let index = 0; index < localizedSegments.length; index += 1) {
170
+ const segment = localizedSegments[index];
171
+ const dynamicMatch = /^\[([^\]/]+)\]$/.exec(segment);
172
+ if (!dynamicMatch) {
173
+ continue;
174
+ }
175
+ if (localeSegmentIndex !== -1) {
176
+ throw new Error(
177
+ `Route template "${normalizedRouteTemplate}" must contain exactly one dynamic locale segment.`
178
+ );
179
+ }
180
+ localeSegmentIndex = index;
181
+ localeParamName = dynamicMatch[1];
182
+ }
183
+ if (localeSegmentIndex === -1) {
184
+ throw new Error(
185
+ `Route template "${normalizedRouteTemplate}" must contain one dynamic locale segment like "[lang]".`
186
+ );
187
+ }
188
+ const deLocalizedSegments = localizedSegments.filter(
189
+ (_, index) => index !== localeSegmentIndex
190
+ );
191
+ const scopePrefixSegments = localizedSegments.slice(0, localeSegmentIndex);
192
+ return {
193
+ deLocalizedSegments,
194
+ localeParamName,
195
+ localeSegmentIndex,
196
+ localizedSegments,
197
+ routeTemplate: normalizedRouteTemplate,
198
+ scopePrefix: joinPathname(scopePrefixSegments)
199
+ };
200
+ }
201
+ function validateRouting(config) {
202
+ if (config.locales.length === 0) {
203
+ throw new Error("defineRouting(...) requires at least one locale.");
204
+ }
205
+ parseRouteTemplate(config.routeTemplate);
206
+ const localeSet = /* @__PURE__ */ new Set();
207
+ for (const locale of config.locales) {
208
+ if (localeSet.has(locale)) {
209
+ throw new Error(`Duplicate locale "${locale}" found in routing config.`);
210
+ }
211
+ localeSet.add(locale);
212
+ }
213
+ if (!hasLocale(config.locales, config.defaultLocale)) {
214
+ throw new Error(
215
+ `The default locale "${config.defaultLocale}" is not included in locales.`
216
+ );
217
+ }
218
+ if (!config.domains) {
219
+ return;
220
+ }
221
+ const domainSet = /* @__PURE__ */ new Set();
222
+ const localeToDomain = /* @__PURE__ */ new Map();
223
+ for (const domain of config.domains) {
224
+ const normalizedDomain = normalizeHost(domain.domain);
225
+ if (!normalizedDomain) {
226
+ throw new Error("Routing domains must include a non-empty domain value.");
227
+ }
228
+ if (domainSet.has(normalizedDomain)) {
229
+ throw new Error(
230
+ `Duplicate domain "${normalizedDomain}" found in routing config.`
231
+ );
232
+ }
233
+ domainSet.add(normalizedDomain);
234
+ if (!hasLocale(config.locales, domain.defaultLocale)) {
235
+ throw new Error(
236
+ `The domain default locale "${domain.defaultLocale}" is not included in locales.`
237
+ );
238
+ }
239
+ const domainLocales = getDomainLocales(domain);
240
+ if (!domainLocales.includes(domain.defaultLocale)) {
241
+ throw new Error(
242
+ `The domain "${domain.domain}" must include its default locale "${domain.defaultLocale}".`
243
+ );
244
+ }
245
+ for (const locale of domainLocales) {
246
+ if (!hasLocale(config.locales, locale)) {
247
+ throw new Error(
248
+ `The domain "${domain.domain}" includes unsupported locale "${locale}".`
249
+ );
250
+ }
251
+ const existingDomain = localeToDomain.get(locale);
252
+ if (existingDomain && existingDomain !== normalizedDomain) {
253
+ throw new Error(
254
+ `Locale "${locale}" is assigned to multiple domains: "${existingDomain}" and "${normalizedDomain}".`
255
+ );
256
+ }
257
+ localeToDomain.set(locale, normalizedDomain);
258
+ }
259
+ }
260
+ }
261
+ function splitPathname(pathname) {
262
+ return normalizePathname(pathname).split("/").filter(Boolean);
263
+ }
264
+ function joinPathname(segments) {
265
+ if (segments.length === 0) {
266
+ return "/";
267
+ }
268
+ return `/${segments.join("/")}`;
269
+ }
270
+ function getDomainLocales(domain) {
271
+ if (!domain.locales || domain.locales.length === 0) {
272
+ return [domain.defaultLocale];
273
+ }
274
+ if (!domain.locales.includes(domain.defaultLocale)) {
275
+ return [...domain.locales, domain.defaultLocale];
276
+ }
277
+ return domain.locales;
278
+ }
279
+
280
+ export {
281
+ hasLocale,
282
+ defineRouting,
283
+ getPathnameLocale,
284
+ isPathnameInScope,
285
+ stripLocaleFromPathname,
286
+ localizePathname,
287
+ getDomainForLocale,
288
+ getLocaleFromDomain,
289
+ buildDomainAwareHref,
290
+ parseRouteTemplate
291
+ };
package/dist/index.cjs ADDED
@@ -0,0 +1,267 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ defineRouting: () => defineRouting,
24
+ getDomainForLocale: () => getDomainForLocale,
25
+ getLocaleFromDomain: () => getLocaleFromDomain,
26
+ getPathnameLocale: () => getPathnameLocale,
27
+ hasLocale: () => hasLocale,
28
+ isPathnameInScope: () => isPathnameInScope,
29
+ localizePathname: () => localizePathname,
30
+ stripLocaleFromPathname: () => stripLocaleFromPathname
31
+ });
32
+ module.exports = __toCommonJS(index_exports);
33
+
34
+ // src/shared.ts
35
+ var DEFAULT_ROUTE_TEMPLATE = "/[lang]";
36
+ function hasLocale(locales, value) {
37
+ return locales.includes(value);
38
+ }
39
+ function defineRouting(config) {
40
+ const routeTemplate = config.routeTemplate ?? DEFAULT_ROUTE_TEMPLATE;
41
+ validateRouting({
42
+ ...config,
43
+ routeTemplate
44
+ });
45
+ const domains = config.domains?.map(
46
+ (domain) => Object.freeze({
47
+ ...domain,
48
+ locales: domain.locales ? [...domain.locales] : void 0
49
+ })
50
+ );
51
+ return Object.freeze({
52
+ locales: [...config.locales],
53
+ defaultLocale: config.defaultLocale,
54
+ domains,
55
+ routeTemplate
56
+ });
57
+ }
58
+ function normalizePathname(pathname) {
59
+ if (!pathname) {
60
+ return "/";
61
+ }
62
+ return pathname.startsWith("/") ? pathname : `/${pathname}`;
63
+ }
64
+ function getPathnameLocale(pathname, routing) {
65
+ const parsedTemplate = parseRouteTemplate(routing.routeTemplate);
66
+ const pathnameSegments = splitPathname(pathname);
67
+ if (pathnameSegments.length < parsedTemplate.localizedSegments.length) {
68
+ return void 0;
69
+ }
70
+ for (let index = 0; index < parsedTemplate.localizedSegments.length; index += 1) {
71
+ const templateSegment = parsedTemplate.localizedSegments[index];
72
+ const pathnameSegment = pathnameSegments[index];
73
+ if (index === parsedTemplate.localeSegmentIndex) {
74
+ if (!pathnameSegment || !hasLocale(routing.locales, pathnameSegment)) {
75
+ return void 0;
76
+ }
77
+ continue;
78
+ }
79
+ if (pathnameSegment !== templateSegment) {
80
+ return void 0;
81
+ }
82
+ }
83
+ return pathnameSegments[parsedTemplate.localeSegmentIndex];
84
+ }
85
+ function isPathnameInScope(pathname, routing) {
86
+ if (getPathnameLocale(pathname, routing)) {
87
+ return true;
88
+ }
89
+ const parsedTemplate = parseRouteTemplate(routing.routeTemplate);
90
+ const pathnameSegments = splitPathname(pathname);
91
+ if (pathnameSegments.length < parsedTemplate.deLocalizedSegments.length) {
92
+ return false;
93
+ }
94
+ for (let index = 0; index < parsedTemplate.deLocalizedSegments.length; index += 1) {
95
+ if (pathnameSegments[index] !== parsedTemplate.deLocalizedSegments[index]) {
96
+ return false;
97
+ }
98
+ }
99
+ return true;
100
+ }
101
+ function stripLocaleFromPathname(pathname, routing) {
102
+ const locale = getPathnameLocale(pathname, routing);
103
+ if (!locale) {
104
+ return normalizePathname(pathname);
105
+ }
106
+ const parsedTemplate = parseRouteTemplate(routing.routeTemplate);
107
+ const pathnameSegments = splitPathname(pathname);
108
+ pathnameSegments.splice(parsedTemplate.localeSegmentIndex, 1);
109
+ return joinPathname(pathnameSegments);
110
+ }
111
+ function localizePathname(pathname, locale, routing) {
112
+ const normalizedPathname = normalizePathname(pathname);
113
+ const deLocalizedPathname = stripLocaleFromPathname(
114
+ normalizedPathname,
115
+ routing
116
+ );
117
+ if (!isPathnameInScope(deLocalizedPathname, routing)) {
118
+ return normalizedPathname;
119
+ }
120
+ const parsedTemplate = parseRouteTemplate(routing.routeTemplate);
121
+ const pathnameSegments = splitPathname(deLocalizedPathname);
122
+ const localizedSegments = [...pathnameSegments];
123
+ localizedSegments.splice(parsedTemplate.localeSegmentIndex, 0, locale);
124
+ return joinPathname(localizedSegments);
125
+ }
126
+ function normalizeHost(host) {
127
+ return host.toLowerCase().replace(/:\d+$/, "");
128
+ }
129
+ function getDomainForLocale(routing, locale) {
130
+ return routing.domains?.find(
131
+ (domain) => getDomainLocales(domain).includes(locale)
132
+ );
133
+ }
134
+ function getLocaleFromDomain(routing, host) {
135
+ const normalizedHost = normalizeHost(host);
136
+ return routing.domains?.find(
137
+ (domain) => normalizeHost(domain.domain) === normalizedHost
138
+ )?.defaultLocale;
139
+ }
140
+ function parseRouteTemplate(routeTemplate) {
141
+ const normalizedRouteTemplate = normalizePathname(
142
+ routeTemplate ?? DEFAULT_ROUTE_TEMPLATE
143
+ );
144
+ const localizedSegments = splitPathname(normalizedRouteTemplate);
145
+ let localeSegmentIndex = -1;
146
+ let localeParamName = "";
147
+ for (let index = 0; index < localizedSegments.length; index += 1) {
148
+ const segment = localizedSegments[index];
149
+ const dynamicMatch = /^\[([^\]/]+)\]$/.exec(segment);
150
+ if (!dynamicMatch) {
151
+ continue;
152
+ }
153
+ if (localeSegmentIndex !== -1) {
154
+ throw new Error(
155
+ `Route template "${normalizedRouteTemplate}" must contain exactly one dynamic locale segment.`
156
+ );
157
+ }
158
+ localeSegmentIndex = index;
159
+ localeParamName = dynamicMatch[1];
160
+ }
161
+ if (localeSegmentIndex === -1) {
162
+ throw new Error(
163
+ `Route template "${normalizedRouteTemplate}" must contain one dynamic locale segment like "[lang]".`
164
+ );
165
+ }
166
+ const deLocalizedSegments = localizedSegments.filter(
167
+ (_, index) => index !== localeSegmentIndex
168
+ );
169
+ const scopePrefixSegments = localizedSegments.slice(0, localeSegmentIndex);
170
+ return {
171
+ deLocalizedSegments,
172
+ localeParamName,
173
+ localeSegmentIndex,
174
+ localizedSegments,
175
+ routeTemplate: normalizedRouteTemplate,
176
+ scopePrefix: joinPathname(scopePrefixSegments)
177
+ };
178
+ }
179
+ function validateRouting(config) {
180
+ if (config.locales.length === 0) {
181
+ throw new Error("defineRouting(...) requires at least one locale.");
182
+ }
183
+ parseRouteTemplate(config.routeTemplate);
184
+ const localeSet = /* @__PURE__ */ new Set();
185
+ for (const locale of config.locales) {
186
+ if (localeSet.has(locale)) {
187
+ throw new Error(`Duplicate locale "${locale}" found in routing config.`);
188
+ }
189
+ localeSet.add(locale);
190
+ }
191
+ if (!hasLocale(config.locales, config.defaultLocale)) {
192
+ throw new Error(
193
+ `The default locale "${config.defaultLocale}" is not included in locales.`
194
+ );
195
+ }
196
+ if (!config.domains) {
197
+ return;
198
+ }
199
+ const domainSet = /* @__PURE__ */ new Set();
200
+ const localeToDomain = /* @__PURE__ */ new Map();
201
+ for (const domain of config.domains) {
202
+ const normalizedDomain = normalizeHost(domain.domain);
203
+ if (!normalizedDomain) {
204
+ throw new Error("Routing domains must include a non-empty domain value.");
205
+ }
206
+ if (domainSet.has(normalizedDomain)) {
207
+ throw new Error(
208
+ `Duplicate domain "${normalizedDomain}" found in routing config.`
209
+ );
210
+ }
211
+ domainSet.add(normalizedDomain);
212
+ if (!hasLocale(config.locales, domain.defaultLocale)) {
213
+ throw new Error(
214
+ `The domain default locale "${domain.defaultLocale}" is not included in locales.`
215
+ );
216
+ }
217
+ const domainLocales = getDomainLocales(domain);
218
+ if (!domainLocales.includes(domain.defaultLocale)) {
219
+ throw new Error(
220
+ `The domain "${domain.domain}" must include its default locale "${domain.defaultLocale}".`
221
+ );
222
+ }
223
+ for (const locale of domainLocales) {
224
+ if (!hasLocale(config.locales, locale)) {
225
+ throw new Error(
226
+ `The domain "${domain.domain}" includes unsupported locale "${locale}".`
227
+ );
228
+ }
229
+ const existingDomain = localeToDomain.get(locale);
230
+ if (existingDomain && existingDomain !== normalizedDomain) {
231
+ throw new Error(
232
+ `Locale "${locale}" is assigned to multiple domains: "${existingDomain}" and "${normalizedDomain}".`
233
+ );
234
+ }
235
+ localeToDomain.set(locale, normalizedDomain);
236
+ }
237
+ }
238
+ }
239
+ function splitPathname(pathname) {
240
+ return normalizePathname(pathname).split("/").filter(Boolean);
241
+ }
242
+ function joinPathname(segments) {
243
+ if (segments.length === 0) {
244
+ return "/";
245
+ }
246
+ return `/${segments.join("/")}`;
247
+ }
248
+ function getDomainLocales(domain) {
249
+ if (!domain.locales || domain.locales.length === 0) {
250
+ return [domain.defaultLocale];
251
+ }
252
+ if (!domain.locales.includes(domain.defaultLocale)) {
253
+ return [...domain.locales, domain.defaultLocale];
254
+ }
255
+ return domain.locales;
256
+ }
257
+ // Annotate the CommonJS export names for ESM import in node:
258
+ 0 && (module.exports = {
259
+ defineRouting,
260
+ getDomainForLocale,
261
+ getLocaleFromDomain,
262
+ getPathnameLocale,
263
+ hasLocale,
264
+ isPathnameInScope,
265
+ localizePathname,
266
+ stripLocaleFromPathname
267
+ });
@@ -0,0 +1,33 @@
1
+ interface RoutingDomain<TLocale extends string> {
2
+ domain: string;
3
+ defaultLocale: TLocale;
4
+ locales?: readonly TLocale[];
5
+ protocol?: "http" | "https";
6
+ }
7
+ interface RoutingConfig<TLocale extends string> {
8
+ locales: readonly TLocale[];
9
+ defaultLocale: TLocale;
10
+ domains?: readonly RoutingDomain<TLocale>[];
11
+ routeTemplate?: string;
12
+ }
13
+ type DefinedRouting<TLocale extends string, TDefaultLocale extends TLocale = TLocale> = Readonly<{
14
+ locales: readonly TLocale[];
15
+ defaultLocale: TDefaultLocale;
16
+ domains?: readonly RoutingDomain<TLocale>[];
17
+ routeTemplate: string;
18
+ }>;
19
+ declare function hasLocale<TLocale extends string>(locales: readonly TLocale[], value: string): value is TLocale;
20
+ declare function defineRouting<const TLocales extends readonly string[], const TDefaultLocale extends TLocales[number]>(config: {
21
+ locales: TLocales;
22
+ defaultLocale: TDefaultLocale;
23
+ domains?: readonly RoutingDomain<TLocales[number]>[];
24
+ routeTemplate?: string;
25
+ }): DefinedRouting<TLocales[number], TDefaultLocale>;
26
+ declare function getPathnameLocale<TLocale extends string>(pathname: string, routing: RoutingConfig<TLocale>): TLocale | undefined;
27
+ declare function isPathnameInScope<TLocale extends string>(pathname: string, routing: RoutingConfig<TLocale>): boolean;
28
+ declare function stripLocaleFromPathname<TLocale extends string>(pathname: string, routing: RoutingConfig<TLocale>): string;
29
+ declare function localizePathname<TLocale extends string>(pathname: string, locale: TLocale, routing: RoutingConfig<TLocale>): string;
30
+ declare function getDomainForLocale<TLocale extends string>(routing: RoutingConfig<TLocale>, locale: TLocale): RoutingDomain<TLocale> | undefined;
31
+ declare function getLocaleFromDomain<TLocale extends string>(routing: RoutingConfig<TLocale>, host: string): TLocale | undefined;
32
+
33
+ export { type DefinedRouting, type RoutingConfig, type RoutingDomain, defineRouting, getDomainForLocale, getLocaleFromDomain, getPathnameLocale, hasLocale, isPathnameInScope, localizePathname, stripLocaleFromPathname };
@@ -0,0 +1,33 @@
1
+ interface RoutingDomain<TLocale extends string> {
2
+ domain: string;
3
+ defaultLocale: TLocale;
4
+ locales?: readonly TLocale[];
5
+ protocol?: "http" | "https";
6
+ }
7
+ interface RoutingConfig<TLocale extends string> {
8
+ locales: readonly TLocale[];
9
+ defaultLocale: TLocale;
10
+ domains?: readonly RoutingDomain<TLocale>[];
11
+ routeTemplate?: string;
12
+ }
13
+ type DefinedRouting<TLocale extends string, TDefaultLocale extends TLocale = TLocale> = Readonly<{
14
+ locales: readonly TLocale[];
15
+ defaultLocale: TDefaultLocale;
16
+ domains?: readonly RoutingDomain<TLocale>[];
17
+ routeTemplate: string;
18
+ }>;
19
+ declare function hasLocale<TLocale extends string>(locales: readonly TLocale[], value: string): value is TLocale;
20
+ declare function defineRouting<const TLocales extends readonly string[], const TDefaultLocale extends TLocales[number]>(config: {
21
+ locales: TLocales;
22
+ defaultLocale: TDefaultLocale;
23
+ domains?: readonly RoutingDomain<TLocales[number]>[];
24
+ routeTemplate?: string;
25
+ }): DefinedRouting<TLocales[number], TDefaultLocale>;
26
+ declare function getPathnameLocale<TLocale extends string>(pathname: string, routing: RoutingConfig<TLocale>): TLocale | undefined;
27
+ declare function isPathnameInScope<TLocale extends string>(pathname: string, routing: RoutingConfig<TLocale>): boolean;
28
+ declare function stripLocaleFromPathname<TLocale extends string>(pathname: string, routing: RoutingConfig<TLocale>): string;
29
+ declare function localizePathname<TLocale extends string>(pathname: string, locale: TLocale, routing: RoutingConfig<TLocale>): string;
30
+ declare function getDomainForLocale<TLocale extends string>(routing: RoutingConfig<TLocale>, locale: TLocale): RoutingDomain<TLocale> | undefined;
31
+ declare function getLocaleFromDomain<TLocale extends string>(routing: RoutingConfig<TLocale>, host: string): TLocale | undefined;
32
+
33
+ export { type DefinedRouting, type RoutingConfig, type RoutingDomain, defineRouting, getDomainForLocale, getLocaleFromDomain, getPathnameLocale, hasLocale, isPathnameInScope, localizePathname, stripLocaleFromPathname };
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ import {
2
+ defineRouting,
3
+ getDomainForLocale,
4
+ getLocaleFromDomain,
5
+ getPathnameLocale,
6
+ hasLocale,
7
+ isPathnameInScope,
8
+ localizePathname,
9
+ stripLocaleFromPathname
10
+ } from "./chunk-VW2LUTE7.js";
11
+ export {
12
+ defineRouting,
13
+ getDomainForLocale,
14
+ getLocaleFromDomain,
15
+ getPathnameLocale,
16
+ hasLocale,
17
+ isPathnameInScope,
18
+ localizePathname,
19
+ stripLocaleFromPathname
20
+ };