@regardio/js 0.5.0 → 0.7.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 +1 -1
- package/dist/assert/index.d.mts +45 -0
- package/dist/assert/index.mjs +57 -0
- package/dist/encoding/index.d.mts +14 -0
- package/dist/encoding/index.mjs +25 -0
- package/dist/http/index.d.mts +53 -0
- package/dist/http/index.mjs +122 -0
- package/dist/intl/index.d.mts +92 -0
- package/dist/intl/index.mjs +124 -0
- package/dist/text/index.d.mts +48 -0
- package/dist/text/index.mjs +223 -0
- package/dist/time/index.d.mts +22 -0
- package/dist/time/index.mjs +154 -0
- package/package.json +57 -52
- package/dist/assert/index.d.ts +0 -43
- package/dist/assert/index.js +0 -22
- package/dist/encoding/index.d.ts +0 -8
- package/dist/encoding/index.js +0 -13
- package/dist/http/index.d.ts +0 -49
- package/dist/http/index.js +0 -126
- package/dist/intl/index.d.ts +0 -91
- package/dist/intl/index.js +0 -139
- package/dist/text/index.d.ts +0 -46
- package/dist/text/index.js +0 -109
- package/dist/time/index.d.ts +0 -48
- package/dist/time/index.js +0 -157
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
Copyright ©
|
|
3
|
+
Copyright © 2026 Regardio
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
6
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/assert/invariant.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Provide a condition and if that condition is falsey, this throws an error
|
|
4
|
+
* with the given message.
|
|
5
|
+
*
|
|
6
|
+
* inspired by invariant from 'tiny-invariant' except will still include the
|
|
7
|
+
* message in production.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* invariant(typeof value === 'string', `value must be a string`)
|
|
11
|
+
*
|
|
12
|
+
* @param condition The condition to check
|
|
13
|
+
* @param message The message to throw (or a callback to generate the message)
|
|
14
|
+
*
|
|
15
|
+
* @throws {Error} if condition is falsey
|
|
16
|
+
*/
|
|
17
|
+
declare function invariant(condition: unknown, message: string | (() => string)): asserts condition;
|
|
18
|
+
/**
|
|
19
|
+
* Provide a condition and if that condition is falsey, this throws a 400
|
|
20
|
+
* Response with the given message.
|
|
21
|
+
*
|
|
22
|
+
* inspired by invariant from 'tiny-invariant'
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* invariantResponse(typeof value === 'string', `value must be a string`)
|
|
26
|
+
*
|
|
27
|
+
* @param condition The condition to check
|
|
28
|
+
* @param message The message to throw (or a callback to generate the message)
|
|
29
|
+
* @param responseInit Additional response init options if a response is thrown
|
|
30
|
+
*
|
|
31
|
+
* @throws {Response} if condition is falsey
|
|
32
|
+
*/
|
|
33
|
+
declare function invariantResponse(condition: unknown, message: string | (() => string), responseInit?: ResponseInit): asserts condition;
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/assert/verify-file-accept.d.ts
|
|
36
|
+
/**
|
|
37
|
+
* Check if a mime type matches the set given in accept
|
|
38
|
+
*
|
|
39
|
+
* @param type the mime type to test, ex image/png
|
|
40
|
+
* @param accept the mime types to accept, ex audio/*,video/*,image/png
|
|
41
|
+
* @returns true if the mime is accepted, false otherwise
|
|
42
|
+
*/
|
|
43
|
+
declare function verifyAccept(type: string, accept: string): boolean;
|
|
44
|
+
//#endregion
|
|
45
|
+
export { invariant, invariantResponse, verifyAccept };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//#region src/assert/invariant.ts
|
|
2
|
+
/**
|
|
3
|
+
* Provide a condition and if that condition is falsey, this throws an error
|
|
4
|
+
* with the given message.
|
|
5
|
+
*
|
|
6
|
+
* inspired by invariant from 'tiny-invariant' except will still include the
|
|
7
|
+
* message in production.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* invariant(typeof value === 'string', `value must be a string`)
|
|
11
|
+
*
|
|
12
|
+
* @param condition The condition to check
|
|
13
|
+
* @param message The message to throw (or a callback to generate the message)
|
|
14
|
+
*
|
|
15
|
+
* @throws {Error} if condition is falsey
|
|
16
|
+
*/
|
|
17
|
+
function invariant(condition, message) {
|
|
18
|
+
if (!condition) throw new Error(typeof message === "function" ? message() : message);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Provide a condition and if that condition is falsey, this throws a 400
|
|
22
|
+
* Response with the given message.
|
|
23
|
+
*
|
|
24
|
+
* inspired by invariant from 'tiny-invariant'
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* invariantResponse(typeof value === 'string', `value must be a string`)
|
|
28
|
+
*
|
|
29
|
+
* @param condition The condition to check
|
|
30
|
+
* @param message The message to throw (or a callback to generate the message)
|
|
31
|
+
* @param responseInit Additional response init options if a response is thrown
|
|
32
|
+
*
|
|
33
|
+
* @throws {Response} if condition is falsey
|
|
34
|
+
*/
|
|
35
|
+
function invariantResponse(condition, message, responseInit) {
|
|
36
|
+
if (!condition) throw new Response(typeof message === "function" ? message() : message, {
|
|
37
|
+
status: 400,
|
|
38
|
+
...responseInit
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/assert/verify-file-accept.ts
|
|
44
|
+
/**
|
|
45
|
+
* Check if a mime type matches the set given in accept
|
|
46
|
+
*
|
|
47
|
+
* @param type the mime type to test, ex image/png
|
|
48
|
+
* @param accept the mime types to accept, ex audio/*,video/*,image/png
|
|
49
|
+
* @returns true if the mime is accepted, false otherwise
|
|
50
|
+
*/
|
|
51
|
+
function verifyAccept(type, accept) {
|
|
52
|
+
const allowed = accept.split(",").map((x) => x.trim());
|
|
53
|
+
return allowed.includes(type) || allowed.includes(`${type.split("/")[0]}/*`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
//#endregion
|
|
57
|
+
export { invariant, invariantResponse, verifyAccept };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/encoding/base64.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Converts a base64 string to a Uint8Array
|
|
4
|
+
* @param base64String The base64 string to convert
|
|
5
|
+
* @returns A Uint8Array containing the decoded data
|
|
6
|
+
*/
|
|
7
|
+
declare const urlBase64ToUint8Array: (base64String: string) => Uint8Array;
|
|
8
|
+
/**
|
|
9
|
+
* Generate a cryptographically secure random base64 string.
|
|
10
|
+
* @returns A base64-encoded random string (16 bytes of entropy)
|
|
11
|
+
*/
|
|
12
|
+
declare function generateRandomBase64(): string;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { generateRandomBase64, urlBase64ToUint8Array };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/encoding/base64.ts
|
|
2
|
+
/**
|
|
3
|
+
* Converts a base64 string to a Uint8Array
|
|
4
|
+
* @param base64String The base64 string to convert
|
|
5
|
+
* @returns A Uint8Array containing the decoded data
|
|
6
|
+
*/
|
|
7
|
+
const urlBase64ToUint8Array = (base64String) => {
|
|
8
|
+
const base64 = (base64String + "=".repeat((4 - base64String.length % 4) % 4)).replace(/-/g, "+").replace(/_/g, "/");
|
|
9
|
+
const rawData = window.atob(base64);
|
|
10
|
+
const outputArray = new Uint8Array(rawData.length);
|
|
11
|
+
for (let i = 0; i < rawData.length; ++i) outputArray[i] = rawData.charCodeAt(i);
|
|
12
|
+
return outputArray;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Generate a cryptographically secure random base64 string.
|
|
16
|
+
* @returns A base64-encoded random string (16 bytes of entropy)
|
|
17
|
+
*/
|
|
18
|
+
function generateRandomBase64() {
|
|
19
|
+
const array = new Uint8Array(16);
|
|
20
|
+
crypto.getRandomValues(array);
|
|
21
|
+
return btoa(String.fromCharCode(...array));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
export { generateRandomBase64, urlBase64ToUint8Array };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//#region src/http/cookie.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Helper function to set cookies in a more controlled way
|
|
4
|
+
* @param name - The name of the cookie
|
|
5
|
+
* @param value - The value to set
|
|
6
|
+
* @param options - Cookie options
|
|
7
|
+
*/
|
|
8
|
+
declare function setCookieValue(name: string, value: string, options?: {
|
|
9
|
+
expires?: Date;
|
|
10
|
+
path?: string;
|
|
11
|
+
sameSite?: string;
|
|
12
|
+
secure?: boolean;
|
|
13
|
+
domain?: string;
|
|
14
|
+
}): void;
|
|
15
|
+
/**
|
|
16
|
+
* Get a cookie value by name
|
|
17
|
+
* @param name - The name of the cookie to get
|
|
18
|
+
* @returns The cookie value or null if not found
|
|
19
|
+
*/
|
|
20
|
+
declare function getCookieValue(name: string): string | null;
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/http/domain.d.ts
|
|
23
|
+
/**
|
|
24
|
+
* Helper utility used to extract the domain from the request even if it's
|
|
25
|
+
* behind a proxy. This is useful for sitemaps and other things.
|
|
26
|
+
* @param request Request object
|
|
27
|
+
* @returns Current domain
|
|
28
|
+
*/
|
|
29
|
+
declare const createDomain: (request: Request) => string;
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/http/is-route-active.d.ts
|
|
32
|
+
/**
|
|
33
|
+
* @name isRouteActive
|
|
34
|
+
* @description A function to check if a route is active. This is used to
|
|
35
|
+
* @param end
|
|
36
|
+
* @param path
|
|
37
|
+
* @param currentPath
|
|
38
|
+
*/
|
|
39
|
+
declare function isRouteActive(path: string, currentPath: string, end?: boolean | ((path: string) => boolean) | undefined): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* @name checkIfRouteIsActive
|
|
42
|
+
* @description A function to check if a route is active. This is used to
|
|
43
|
+
* highlight the active link in the navigation.
|
|
44
|
+
* @param targetLink - The link to check against
|
|
45
|
+
* @param currentRoute - the current route
|
|
46
|
+
* @param depth - how far down should segments be matched?
|
|
47
|
+
*/
|
|
48
|
+
declare function checkIfRouteIsActive(targetLink: string, currentRoute: string, depth?: number): boolean;
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/http/request-helpers.d.ts
|
|
51
|
+
declare function getCleanUrl(request: Request): string;
|
|
52
|
+
//#endregion
|
|
53
|
+
export { checkIfRouteIsActive, createDomain, getCleanUrl, getCookieValue, isRouteActive, setCookieValue };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
//#region src/http/cookie.ts
|
|
2
|
+
/**
|
|
3
|
+
* Helper function to set cookies in a more controlled way
|
|
4
|
+
* @param name - The name of the cookie
|
|
5
|
+
* @param value - The value to set
|
|
6
|
+
* @param options - Cookie options
|
|
7
|
+
*/
|
|
8
|
+
function setCookieValue(name, value, options = {}) {
|
|
9
|
+
if (typeof window === "undefined") {
|
|
10
|
+
console.warn("Cannot set cookie on server side");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
let cookieString = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
14
|
+
if (options.expires) cookieString += `; expires=${options.expires.toUTCString()}`;
|
|
15
|
+
if (options.path) cookieString += `; path=${options.path}`;
|
|
16
|
+
if (options.sameSite) cookieString += `; SameSite=${options.sameSite}`;
|
|
17
|
+
if (options.secure) cookieString += "; Secure";
|
|
18
|
+
if (options.domain) cookieString += `; domain=${options.domain}`;
|
|
19
|
+
try {
|
|
20
|
+
document.cookie = cookieString;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (error instanceof Error) console.error(`Failed to set cookie '${name}':`, error.message);
|
|
23
|
+
else console.error(`Failed to set cookie '${name}': Unknown error`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get a cookie value by name
|
|
28
|
+
* @param name - The name of the cookie to get
|
|
29
|
+
* @returns The cookie value or null if not found
|
|
30
|
+
*/
|
|
31
|
+
function getCookieValue(name) {
|
|
32
|
+
if (typeof window === "undefined") {
|
|
33
|
+
console.warn("Cannot get cookie on server side");
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const parts = `; ${document.cookie}`.split(`; ${name}=`);
|
|
37
|
+
if (parts.length === 2) {
|
|
38
|
+
const cookieValue = parts.pop()?.split(";").shift();
|
|
39
|
+
return cookieValue ? decodeURIComponent(cookieValue) : null;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/http/domain.ts
|
|
46
|
+
/**
|
|
47
|
+
* Helper utility used to extract the domain from the request even if it's
|
|
48
|
+
* behind a proxy. This is useful for sitemaps and other things.
|
|
49
|
+
* @param request Request object
|
|
50
|
+
* @returns Current domain
|
|
51
|
+
*/
|
|
52
|
+
const createDomain = (request) => {
|
|
53
|
+
const headers = request.headers;
|
|
54
|
+
const maybeProto = headers.get("x-forwarded-proto");
|
|
55
|
+
const maybeHost = headers.get("host");
|
|
56
|
+
const url = new URL(request.url);
|
|
57
|
+
if (maybeProto) return `${maybeProto}://${maybeHost ?? url.host}`;
|
|
58
|
+
if (url.hostname === "localhost") return `http://${url.host}`;
|
|
59
|
+
return `https://${url.host}`;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/http/is-route-active.ts
|
|
64
|
+
const ROOT_PATH = "/";
|
|
65
|
+
/**
|
|
66
|
+
* @name isRouteActive
|
|
67
|
+
* @description A function to check if a route is active. This is used to
|
|
68
|
+
* @param end
|
|
69
|
+
* @param path
|
|
70
|
+
* @param currentPath
|
|
71
|
+
*/
|
|
72
|
+
function isRouteActive(path, currentPath, end) {
|
|
73
|
+
if (path === currentPath) return true;
|
|
74
|
+
if (typeof end === "function") return !end(currentPath);
|
|
75
|
+
return checkIfRouteIsActive(path, currentPath, end ?? true ? 1 : 3);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* @name checkIfRouteIsActive
|
|
79
|
+
* @description A function to check if a route is active. This is used to
|
|
80
|
+
* highlight the active link in the navigation.
|
|
81
|
+
* @param targetLink - The link to check against
|
|
82
|
+
* @param currentRoute - the current route
|
|
83
|
+
* @param depth - how far down should segments be matched?
|
|
84
|
+
*/
|
|
85
|
+
function checkIfRouteIsActive(targetLink, currentRoute, depth = 1) {
|
|
86
|
+
const currentRoutePath = currentRoute.split("?")[0] ?? "";
|
|
87
|
+
if (!isRoot(currentRoutePath) && isRoot(targetLink)) return false;
|
|
88
|
+
if (!currentRoutePath.includes(targetLink)) return false;
|
|
89
|
+
if (targetLink === currentRoutePath) return true;
|
|
90
|
+
return hasMatchingSegments(targetLink, currentRoutePath, depth);
|
|
91
|
+
}
|
|
92
|
+
function splitIntoSegments(href) {
|
|
93
|
+
return href.split("/").filter(Boolean);
|
|
94
|
+
}
|
|
95
|
+
function hasMatchingSegments(targetLink, currentRoute, depth) {
|
|
96
|
+
const segments = splitIntoSegments(targetLink);
|
|
97
|
+
const matchingSegments = numberOfMatchingSegments(currentRoute, segments);
|
|
98
|
+
if (targetLink === currentRoute) return true;
|
|
99
|
+
return matchingSegments > segments.length - (depth - 1);
|
|
100
|
+
}
|
|
101
|
+
function numberOfMatchingSegments(href, segments) {
|
|
102
|
+
let count = 0;
|
|
103
|
+
for (const segment of splitIntoSegments(href)) if (segments.includes(segment)) count += 1;
|
|
104
|
+
else return count;
|
|
105
|
+
return count;
|
|
106
|
+
}
|
|
107
|
+
function isRoot(path) {
|
|
108
|
+
return path === ROOT_PATH;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/http/request-helpers.ts
|
|
113
|
+
function getCleanUrl(request) {
|
|
114
|
+
const url = new URL(request.url);
|
|
115
|
+
url.searchParams.forEach((_, key) => {
|
|
116
|
+
url.searchParams.delete(key);
|
|
117
|
+
});
|
|
118
|
+
return url.toString();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
export { checkIfRouteIsActive, createDomain, getCleanUrl, getCookieValue, isRouteActive, setCookieValue };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Cookie, SessionStorage } from "react-router";
|
|
2
|
+
|
|
3
|
+
//#region src/intl/language-detector.d.ts
|
|
4
|
+
interface LanguageDetectorOption {
|
|
5
|
+
/**
|
|
6
|
+
* Define the list of supported languages, this is used to determine if one of
|
|
7
|
+
* the languages requested by the user is supported by the application.
|
|
8
|
+
* This should be be same as the supportedLngs in the i18next options.
|
|
9
|
+
*/
|
|
10
|
+
supportedLanguages: string[];
|
|
11
|
+
/**
|
|
12
|
+
* Define the fallback language that it's going to be used in the case user
|
|
13
|
+
* expected language is not supported.
|
|
14
|
+
* This should be be same as the fallbackLng in the i18next options.
|
|
15
|
+
*/
|
|
16
|
+
fallbackLanguage: string;
|
|
17
|
+
/**
|
|
18
|
+
* If you want to use a cookie to store the user preferred language, you can
|
|
19
|
+
* pass the Cookie object here.
|
|
20
|
+
*/
|
|
21
|
+
cookie?: Cookie;
|
|
22
|
+
/**
|
|
23
|
+
* If you want to use a session to store the user preferred language, you can
|
|
24
|
+
* pass the SessionStorage object here.
|
|
25
|
+
* When this is not defined, getting the locale will ignore the session.
|
|
26
|
+
*/
|
|
27
|
+
sessionStorage?: SessionStorage;
|
|
28
|
+
/**
|
|
29
|
+
* If defined a sessionStorage and want to change the default key used to
|
|
30
|
+
* store the user preferred language, you can pass the key here.
|
|
31
|
+
* @default "lng"
|
|
32
|
+
*/
|
|
33
|
+
sessionKey?: string;
|
|
34
|
+
/**
|
|
35
|
+
* If you want to use search parameters for language detection and want to
|
|
36
|
+
* change the default key used to for the parameter name,
|
|
37
|
+
* you can pass the key here.
|
|
38
|
+
* @default "lng"
|
|
39
|
+
*/
|
|
40
|
+
searchParamKey?: string;
|
|
41
|
+
/**
|
|
42
|
+
* The order the library will use to detect the user preferred language.
|
|
43
|
+
* By default the order is
|
|
44
|
+
* - urlPath (first segment like /de/ or /en/)
|
|
45
|
+
* - cookie
|
|
46
|
+
* - session
|
|
47
|
+
* - searchParams
|
|
48
|
+
* - header
|
|
49
|
+
* And finally the fallback language.
|
|
50
|
+
*/
|
|
51
|
+
order?: Array<"urlPath" | "searchParams" | "cookie" | "session" | "header">;
|
|
52
|
+
}
|
|
53
|
+
interface LanguageDetectorLinguiOptions {
|
|
54
|
+
detection: LanguageDetectorOption;
|
|
55
|
+
}
|
|
56
|
+
declare class LanguageDetectorLingui {
|
|
57
|
+
private detector;
|
|
58
|
+
private options;
|
|
59
|
+
constructor(options: LanguageDetectorLinguiOptions);
|
|
60
|
+
/**
|
|
61
|
+
* Detect the current locale by following the order defined in the
|
|
62
|
+
* `detection.order` option.
|
|
63
|
+
* By default the order is
|
|
64
|
+
* - urlPath (first segment like /de/ or /en/)
|
|
65
|
+
* - cookie
|
|
66
|
+
* - session
|
|
67
|
+
* - searchParams
|
|
68
|
+
* - header
|
|
69
|
+
* And finally the fallback language.
|
|
70
|
+
*/
|
|
71
|
+
getLocale(request: Request): Promise<string>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The LanguageDetector contains the logic to detect the user preferred language
|
|
75
|
+
* fully server-side by using a SessionStorage, Cookie, URLSearchParams, or
|
|
76
|
+
* Headers.
|
|
77
|
+
*/
|
|
78
|
+
declare class LanguageDetector {
|
|
79
|
+
private options;
|
|
80
|
+
constructor(options: LanguageDetectorOption);
|
|
81
|
+
detect(request: Request): Promise<string>;
|
|
82
|
+
private isSessionOnly;
|
|
83
|
+
private isCookieOnly;
|
|
84
|
+
private fromUrlPath;
|
|
85
|
+
private fromSearchParams;
|
|
86
|
+
private fromCookie;
|
|
87
|
+
private fromSessionStorage;
|
|
88
|
+
private fromHeader;
|
|
89
|
+
private fromSupported;
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { LanguageDetector, LanguageDetectorLingui, type LanguageDetectorLinguiOptions, type LanguageDetectorOption };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { parseAcceptLanguage } from "intl-parse-accept-language";
|
|
2
|
+
|
|
3
|
+
//#region src/intl/language-detector.ts
|
|
4
|
+
var LanguageDetectorLingui = class {
|
|
5
|
+
detector;
|
|
6
|
+
options;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
this.detector = new LanguageDetector(this.options.detection);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Detect the current locale by following the order defined in the
|
|
13
|
+
* `detection.order` option.
|
|
14
|
+
* By default the order is
|
|
15
|
+
* - urlPath (first segment like /de/ or /en/)
|
|
16
|
+
* - cookie
|
|
17
|
+
* - session
|
|
18
|
+
* - searchParams
|
|
19
|
+
* - header
|
|
20
|
+
* And finally the fallback language.
|
|
21
|
+
*/
|
|
22
|
+
async getLocale(request) {
|
|
23
|
+
return await this.detector.detect(request);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The LanguageDetector contains the logic to detect the user preferred language
|
|
28
|
+
* fully server-side by using a SessionStorage, Cookie, URLSearchParams, or
|
|
29
|
+
* Headers.
|
|
30
|
+
*/
|
|
31
|
+
var LanguageDetector = class {
|
|
32
|
+
options;
|
|
33
|
+
constructor(options) {
|
|
34
|
+
this.options = options;
|
|
35
|
+
this.isSessionOnly(this.options);
|
|
36
|
+
this.isCookieOnly(this.options);
|
|
37
|
+
}
|
|
38
|
+
async detect(request) {
|
|
39
|
+
const order = this.options.order ?? [
|
|
40
|
+
"urlPath",
|
|
41
|
+
"cookie",
|
|
42
|
+
"session",
|
|
43
|
+
"searchParams",
|
|
44
|
+
"header"
|
|
45
|
+
];
|
|
46
|
+
for (const method of order) {
|
|
47
|
+
let locale = null;
|
|
48
|
+
if (method === "urlPath") locale = this.fromUrlPath(request);
|
|
49
|
+
if (method === "searchParams") locale = this.fromSearchParams(request);
|
|
50
|
+
if (method === "cookie") locale = await this.fromCookie(request);
|
|
51
|
+
if (method === "session") locale = await this.fromSessionStorage(request);
|
|
52
|
+
if (method === "header") locale = this.fromHeader(request);
|
|
53
|
+
if (locale) return locale;
|
|
54
|
+
}
|
|
55
|
+
return this.options.fallbackLanguage;
|
|
56
|
+
}
|
|
57
|
+
isSessionOnly(options) {
|
|
58
|
+
if (options.order?.length === 1 && options.order[0] === "session" && !options.sessionStorage) throw new Error("You need a sessionStorage if you want to only get the locale from the session");
|
|
59
|
+
}
|
|
60
|
+
isCookieOnly(options) {
|
|
61
|
+
if (options.order?.length === 1 && options.order[0] === "cookie" && !options.cookie) throw new Error("You need a cookie if you want to only get the locale from the cookie");
|
|
62
|
+
}
|
|
63
|
+
fromUrlPath(request) {
|
|
64
|
+
const pathSegments = new URL(request.url).pathname.split("/").filter(Boolean);
|
|
65
|
+
if (pathSegments.length > 0) {
|
|
66
|
+
const firstSegment = pathSegments[0];
|
|
67
|
+
if (firstSegment) return this.fromSupported(firstSegment);
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
fromSearchParams(request) {
|
|
72
|
+
const url = new URL(request.url);
|
|
73
|
+
if (!url.searchParams.has(this.options.searchParamKey ?? "lng")) return null;
|
|
74
|
+
return this.fromSupported(url.searchParams.get(this.options.searchParamKey ?? "lng"));
|
|
75
|
+
}
|
|
76
|
+
async fromCookie(request) {
|
|
77
|
+
if (!this.options.cookie) return null;
|
|
78
|
+
const cookie = this.options.cookie;
|
|
79
|
+
try {
|
|
80
|
+
const lng = await cookie.parse(request.headers.get("Cookie"));
|
|
81
|
+
if (typeof lng !== "string" || !lng) return null;
|
|
82
|
+
return this.fromSupported(lng);
|
|
83
|
+
} catch (_error) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async fromSessionStorage(request) {
|
|
88
|
+
if (!this.options.sessionStorage) return null;
|
|
89
|
+
const lng = (await this.options.sessionStorage.getSession(request.headers.get("Cookie"))).get(this.options.sessionKey ?? "lng");
|
|
90
|
+
if (!lng) return null;
|
|
91
|
+
return this.fromSupported(lng);
|
|
92
|
+
}
|
|
93
|
+
fromHeader(request) {
|
|
94
|
+
const locales = getClientLocales(request);
|
|
95
|
+
if (!locales) return null;
|
|
96
|
+
if (Array.isArray(locales)) return this.fromSupported(locales.join(","));
|
|
97
|
+
return this.fromSupported(locales);
|
|
98
|
+
}
|
|
99
|
+
fromSupported(language) {
|
|
100
|
+
if (!language) return this.options.fallbackLanguage;
|
|
101
|
+
return parseAcceptLanguage(Array.isArray(language) ? language.join(",") : language, {
|
|
102
|
+
ignoreWildcard: true,
|
|
103
|
+
validate: (locale) => this.options.supportedLanguages.includes(locale) ? locale : null
|
|
104
|
+
})[0] || this.options.fallbackLanguage;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
function getClientLocales(requestOrHeaders) {
|
|
108
|
+
const acceptLanguage = getHeaders(requestOrHeaders).get("Accept-Language");
|
|
109
|
+
if (!acceptLanguage) return void 0;
|
|
110
|
+
const locales = parseAcceptLanguage(acceptLanguage, {
|
|
111
|
+
ignoreWildcard: true,
|
|
112
|
+
validate: Intl.DateTimeFormat.supportedLocalesOf
|
|
113
|
+
});
|
|
114
|
+
if (locales.length === 0) return void 0;
|
|
115
|
+
if (locales.length === 1) return locales[0];
|
|
116
|
+
return locales;
|
|
117
|
+
}
|
|
118
|
+
function getHeaders(requestOrHeaders) {
|
|
119
|
+
if (requestOrHeaders instanceof Request) return requestOrHeaders.headers;
|
|
120
|
+
return requestOrHeaders;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
//#endregion
|
|
124
|
+
export { LanguageDetector, LanguageDetectorLingui };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region src/text/bytes.d.ts
|
|
2
|
+
declare function formatBytes(bytes: number, decimals?: number): string;
|
|
3
|
+
//#endregion
|
|
4
|
+
//#region src/text/text.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Replace straight quotes with typographically correct quotes for the given locale.
|
|
7
|
+
*
|
|
8
|
+
* @param text - The text containing straight quotes
|
|
9
|
+
* @param locale - The locale to use for quote style (e.g., 'en', 'de', 'fr')
|
|
10
|
+
* @returns Text with typographic quotes
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* typographicQuotes('"Hello"', 'en') // → '"Hello"'
|
|
14
|
+
* typographicQuotes('"Hello"', 'de') // → '„Hello"'
|
|
15
|
+
* typographicQuotes('"Hello"', 'fr') // → '« Hello »'
|
|
16
|
+
*/
|
|
17
|
+
declare function typographicQuotes(text: string, locale: string): string;
|
|
18
|
+
declare function toBoolean(value: string | boolean | null | undefined): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Replace ­ HTML entity with Unicode soft hyphen
|
|
21
|
+
*/
|
|
22
|
+
declare function replaceShyInString(input: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Split text into sentences
|
|
25
|
+
*/
|
|
26
|
+
declare function splitIntoSentences(text: string): string[];
|
|
27
|
+
/**
|
|
28
|
+
* Split text into words
|
|
29
|
+
*/
|
|
30
|
+
declare function splitIntoWords(text: string): string[];
|
|
31
|
+
/**
|
|
32
|
+
* Truncate text to a maximum length
|
|
33
|
+
*/
|
|
34
|
+
declare function truncateText(text: string, maxLength: number, suffix?: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Author info parsed from a string like "Name <email> (url)"
|
|
37
|
+
*/
|
|
38
|
+
type AuthorInfo = {
|
|
39
|
+
name?: string;
|
|
40
|
+
email?: string;
|
|
41
|
+
url?: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Parse an author string in the format "Name <email> (url)"
|
|
45
|
+
*/
|
|
46
|
+
declare function parseAuthorString(input: string): AuthorInfo;
|
|
47
|
+
//#endregion
|
|
48
|
+
export { type AuthorInfo, formatBytes, parseAuthorString, replaceShyInString, splitIntoSentences, splitIntoWords, toBoolean, truncateText, typographicQuotes };
|