intor 1.0.39 → 2.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/dist/config/index.cjs +44 -79
- package/dist/config/index.d.cts +69 -36
- package/dist/config/index.d.ts +69 -36
- package/dist/config/index.js +42 -80
- package/dist/index.cjs +482 -540
- package/dist/index.d.cts +163 -139
- package/dist/index.d.ts +163 -139
- package/dist/index.js +475 -537
- package/dist/next/index.cjs +399 -359
- package/dist/next/index.d.cts +82 -113
- package/dist/next/index.d.ts +82 -113
- package/dist/next/index.js +396 -351
- package/dist/next/middleware/index.cjs +79 -93
- package/dist/next/middleware/index.d.cts +59 -28
- package/dist/next/middleware/index.d.ts +59 -28
- package/dist/next/middleware/index.js +79 -93
- package/dist/next/server/index.cjs +886 -0
- package/dist/next/server/index.d.cts +149 -0
- package/dist/next/server/index.d.ts +149 -0
- package/dist/next/server/index.js +875 -0
- package/package.json +9 -12
- package/exports/next/provider/intor-provider.tsx +0 -25
- package/exports/next/provider/translate-handlers-provider.tsx +0 -19
package/dist/index.js
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
|
-
import { logry } from 'logry';
|
|
2
|
-
import { cookies, headers } from 'next/headers';
|
|
3
1
|
import path from 'path';
|
|
4
2
|
import { performance } from 'perf_hooks';
|
|
5
3
|
import pLimit from 'p-limit';
|
|
6
4
|
import fs from 'fs/promises';
|
|
7
|
-
import {
|
|
5
|
+
import { logry } from 'logry';
|
|
6
|
+
import Keyv from 'keyv';
|
|
8
7
|
export { Translator } from 'intor-translator';
|
|
9
8
|
|
|
10
|
-
// src/modules/intor/
|
|
9
|
+
// src/modules/intor/utils/should-load-messages.ts
|
|
10
|
+
var shouldLoadMessages = (loader) => {
|
|
11
|
+
if (!loader) return false;
|
|
12
|
+
const { type, lazyLoad } = loader;
|
|
13
|
+
if (type === "import") return true;
|
|
14
|
+
if (lazyLoad) return false;
|
|
15
|
+
return true;
|
|
16
|
+
};
|
|
11
17
|
|
|
12
|
-
// src/modules/
|
|
18
|
+
// src/modules/config/constants/cache.constants.ts
|
|
19
|
+
var DEFAULT_CACHE_OPTIONS = {
|
|
20
|
+
enabled: process.env.NODE_ENV === "production",
|
|
21
|
+
ttl: 60 * 60 * 1e3
|
|
22
|
+
// 1 hour
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// src/shared/error/intor-error.ts
|
|
13
26
|
var IntorError = class extends Error {
|
|
14
27
|
constructor({ message, code, id }) {
|
|
15
28
|
const fullMessage = id ? `[${id}] ${message}` : message;
|
|
@@ -21,218 +34,95 @@ var IntorError = class extends Error {
|
|
|
21
34
|
}
|
|
22
35
|
};
|
|
23
36
|
|
|
24
|
-
// src/modules/
|
|
25
|
-
var
|
|
26
|
-
IntorErrorCode2["MISSING_DEFAULT_LOCALE"] = "INTOR_MISSING_DEFAULT_LOCALE";
|
|
27
|
-
IntorErrorCode2["UNSUPPORTED_DEFAULT_LOCALE"] = "INTOR_UNSUPPORTED_DEFAULT_LOCALE";
|
|
28
|
-
IntorErrorCode2["MISSING_SUPPORTED_LOCALES"] = "INTOR_MISSING_SUPPORTED_LOCALES";
|
|
29
|
-
IntorErrorCode2["UNSUPPORTED_ADAPTER"] = "INTOR_UNSUPPORTED_ADAPTER";
|
|
30
|
-
IntorErrorCode2["ADAPTER_RUNTIME_LOAD_FAILED"] = "INTOR_ADAPTER_RUNTIME_LOAD_FAILED";
|
|
31
|
-
IntorErrorCode2["UNKNOWN_LOADER_TYPE"] = "INTOR_UNKNOWN_LOADER_TYPE";
|
|
32
|
-
return IntorErrorCode2;
|
|
33
|
-
})(IntorErrorCode || {});
|
|
34
|
-
|
|
35
|
-
// src/adapters/next-client/constants/header-key-constants.ts
|
|
36
|
-
var DEFAULT_PATHNAME_HEADER_NAME = "x-intor-pathname";
|
|
37
|
-
|
|
38
|
-
// src/shared/utils/locale/normalize-locale.ts
|
|
39
|
-
var normalizeLocale = (locale = "", supportedLocales = []) => {
|
|
40
|
-
if (!locale || supportedLocales.length === 0) return void 0;
|
|
41
|
-
const toCanonical = (input) => {
|
|
42
|
-
try {
|
|
43
|
-
return Intl.getCanonicalLocales(input)[0]?.toLowerCase();
|
|
44
|
-
} catch {
|
|
45
|
-
return void 0;
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
const canonicalLocale = toCanonical(locale);
|
|
49
|
-
if (!canonicalLocale) return void 0;
|
|
50
|
-
const supportedCanonicalMap = /* @__PURE__ */ new Map();
|
|
51
|
-
for (const l of supportedLocales) {
|
|
52
|
-
const normalized = toCanonical(l);
|
|
53
|
-
if (normalized) {
|
|
54
|
-
supportedCanonicalMap.set(normalized, l);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
if (supportedCanonicalMap.has(canonicalLocale)) {
|
|
58
|
-
return supportedCanonicalMap.get(canonicalLocale);
|
|
59
|
-
}
|
|
60
|
-
const baseLang = canonicalLocale.split("-")[0];
|
|
61
|
-
for (const [key, original] of supportedCanonicalMap) {
|
|
62
|
-
const supportedBase = key.split("-")[0];
|
|
63
|
-
if (supportedBase === baseLang) {
|
|
64
|
-
return original;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return void 0;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// src/shared/utils/locale/resolve-preferred-locale.ts
|
|
71
|
-
var resolvePreferredLocale = (acceptLanguageHeader, supportedLocales) => {
|
|
72
|
-
if (!acceptLanguageHeader || !supportedLocales || supportedLocales.length === 0) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
const supportedLocalesSet = new Set(supportedLocales);
|
|
76
|
-
const preferred = acceptLanguageHeader.split(",").map((part) => {
|
|
77
|
-
const [lang, qValue] = part.split(";");
|
|
78
|
-
const q = qValue ? parseFloat(qValue.split("=")[1]) : 1;
|
|
79
|
-
if (isNaN(q)) {
|
|
80
|
-
return { lang: lang.trim(), q: 0 };
|
|
81
|
-
}
|
|
82
|
-
return { lang: lang.trim(), q };
|
|
83
|
-
}).sort((a, b) => b.q - a.q).find(({ lang }) => supportedLocalesSet.has(lang))?.lang;
|
|
84
|
-
return preferred;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
// src/adapters/next-client/next-client-runtime/next-client-runtime.ts
|
|
88
|
-
var nextClientRuntime = async ({
|
|
89
|
-
config
|
|
90
|
-
}) => {
|
|
91
|
-
const cookiesStore = await cookies();
|
|
92
|
-
const headersStore = await headers();
|
|
93
|
-
const logger = logry({ id: config.id, scope: "nextClientRuntime" });
|
|
94
|
-
const { defaultLocale, supportedLocales = [], cookie, routing } = config;
|
|
95
|
-
let locale;
|
|
96
|
-
if (!cookie.disabled) {
|
|
97
|
-
const localeFromCookie = cookiesStore.get(cookie.name)?.value;
|
|
98
|
-
locale = normalizeLocale(localeFromCookie, supportedLocales);
|
|
99
|
-
if (locale) {
|
|
100
|
-
logger.debug("Get locale from cookie:", { locale });
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
if (!locale && routing.firstVisit.localeSource === "browser") {
|
|
104
|
-
const aLHeader = headersStore.get("accept-language") || void 0;
|
|
105
|
-
const preferredLocale = resolvePreferredLocale(aLHeader, supportedLocales);
|
|
106
|
-
locale = normalizeLocale(preferredLocale, supportedLocales);
|
|
107
|
-
logger.debug("Get locale from browser:", { locale });
|
|
108
|
-
}
|
|
109
|
-
const pathname = headersStore.get(DEFAULT_PATHNAME_HEADER_NAME);
|
|
110
|
-
if (pathname) {
|
|
111
|
-
logger.debug("Get pathname from next headers:", { pathname });
|
|
112
|
-
}
|
|
113
|
-
return {
|
|
114
|
-
locale: locale || defaultLocale,
|
|
115
|
-
pathname: pathname || ""
|
|
116
|
-
};
|
|
117
|
-
};
|
|
118
|
-
var nextServerRuntime = async ({
|
|
119
|
-
config,
|
|
120
|
-
request
|
|
121
|
-
}) => {
|
|
122
|
-
const cookiesStore = await cookies();
|
|
123
|
-
const headersStore = await headers();
|
|
124
|
-
const logger = logry({ id: config.id, scope: "nextServerRuntime" });
|
|
125
|
-
const { defaultLocale, supportedLocales = [], cookie, routing } = config;
|
|
126
|
-
let locale;
|
|
127
|
-
if (!cookie.disabled) {
|
|
128
|
-
const localeFromCookie = cookiesStore.get(cookie.name)?.value;
|
|
129
|
-
locale = normalizeLocale(localeFromCookie, supportedLocales);
|
|
130
|
-
if (locale) {
|
|
131
|
-
logger.debug("Get locale from cookie:", { locale });
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
if (!locale && routing.firstVisit.localeSource === "browser") {
|
|
135
|
-
const aLHeader = headersStore.get("accept-language") || void 0;
|
|
136
|
-
const preferredLocale = resolvePreferredLocale(aLHeader, supportedLocales);
|
|
137
|
-
locale = normalizeLocale(preferredLocale, supportedLocales);
|
|
138
|
-
logger.debug("Get locale from browser:", { locale });
|
|
139
|
-
}
|
|
140
|
-
let pathname = "";
|
|
141
|
-
if (request && typeof request === "object" && "nextUrl" in request) {
|
|
142
|
-
pathname = request.nextUrl.pathname;
|
|
143
|
-
if (pathname) {
|
|
144
|
-
logger.debug("Get pathname from next request:", { pathname });
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
if (!pathname) {
|
|
148
|
-
pathname = headersStore.get(DEFAULT_PATHNAME_HEADER_NAME);
|
|
149
|
-
if (pathname) {
|
|
150
|
-
logger.debug("Get pathname from next headers:", { pathname });
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
if (!cookie.disabled && cookie.autoSetCookie) {
|
|
154
|
-
cookiesStore.set({
|
|
155
|
-
name: cookie.name,
|
|
156
|
-
value: locale || defaultLocale,
|
|
157
|
-
...cookie.domain ? { domain: cookie.domain } : {},
|
|
158
|
-
path: cookie.path,
|
|
159
|
-
maxAge: cookie.maxAge,
|
|
160
|
-
httpOnly: cookie.httpOnly,
|
|
161
|
-
secure: cookie.secure,
|
|
162
|
-
sameSite: cookie.sameSite
|
|
163
|
-
});
|
|
164
|
-
logger.debug("Set locale to cookie:", {
|
|
165
|
-
cookie: { name: cookie.name, value: locale || defaultLocale }
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
return {
|
|
169
|
-
locale: locale || defaultLocale,
|
|
170
|
-
pathname: pathname || ""
|
|
171
|
-
};
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
// src/modules/intor-adapter/resolve-adapter-runtime-loader.ts
|
|
175
|
-
var resolceAdapterRuntimeLoader = async ({
|
|
176
|
-
config
|
|
177
|
-
}) => {
|
|
178
|
-
const { adapter } = config;
|
|
179
|
-
let loadedRuntime;
|
|
180
|
-
if (adapter === "next-client") {
|
|
181
|
-
loadedRuntime = nextClientRuntime;
|
|
182
|
-
} else if (adapter === "next-server") {
|
|
183
|
-
loadedRuntime = nextServerRuntime;
|
|
184
|
-
}
|
|
185
|
-
return loadedRuntime;
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
// src/modules/intor-config/types/intor-adapter-types.ts
|
|
189
|
-
var intorAdapters = ["next-client", "next-server"];
|
|
190
|
-
var readMessageRecordFile = async (filePath) => {
|
|
37
|
+
// src/modules/messages/load-local-messages/utils/read-message-record-file.ts
|
|
38
|
+
var readMessageRecordFile = async (filePath, loggerOptions) => {
|
|
191
39
|
const fileName = path.basename(filePath, ".json");
|
|
192
40
|
const content = await fs.readFile(filePath, "utf-8");
|
|
193
41
|
const parsed = JSON.parse(content);
|
|
194
42
|
if (typeof parsed !== "object" || parsed === null) {
|
|
195
|
-
throw new
|
|
43
|
+
throw new IntorError({
|
|
44
|
+
id: loggerOptions.id,
|
|
45
|
+
code: "INTOR_INVALID_MESSAGE_FORMAT" /* INVALID_MESSAGE_FORMAT */,
|
|
46
|
+
message: "Invalid message format"
|
|
47
|
+
});
|
|
196
48
|
}
|
|
197
49
|
return { fileName, content: parsed };
|
|
198
50
|
};
|
|
199
51
|
|
|
200
|
-
// src/
|
|
52
|
+
// src/shared/logger/global-logger-pool.ts
|
|
53
|
+
function getGlobalLoggerPool() {
|
|
54
|
+
if (!globalThis.__INTOR_LOGGER_POOL__) {
|
|
55
|
+
globalThis.__INTOR_LOGGER_POOL__ = /* @__PURE__ */ new Map();
|
|
56
|
+
}
|
|
57
|
+
return globalThis.__INTOR_LOGGER_POOL__;
|
|
58
|
+
}
|
|
59
|
+
function clearLoggerPool() {
|
|
60
|
+
const pool = getGlobalLoggerPool();
|
|
61
|
+
pool.clear();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/shared/logger/get-logger.ts
|
|
65
|
+
var DEFAULT_FORMATTER_CONFIG = {
|
|
66
|
+
node: { meta: { compact: true }, lineBreaksAfter: 1 }
|
|
67
|
+
};
|
|
68
|
+
function getLogger({
|
|
69
|
+
id,
|
|
70
|
+
formatterConfig = DEFAULT_FORMATTER_CONFIG,
|
|
71
|
+
...options
|
|
72
|
+
}) {
|
|
73
|
+
const pool = getGlobalLoggerPool();
|
|
74
|
+
let logger = pool.get(id);
|
|
75
|
+
if (!logger) {
|
|
76
|
+
logger = logry({ id, formatterConfig, ...options });
|
|
77
|
+
pool.set(id, logger);
|
|
78
|
+
if (pool.size > 1e3) {
|
|
79
|
+
const keys = Array.from(pool.keys());
|
|
80
|
+
for (const key of keys.slice(0, 200)) pool.delete(key);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return logger;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/modules/messages/load-local-messages/load-namespace-group/parse-message-file.ts
|
|
201
87
|
var MAX_PATH_LENGTH = 260;
|
|
202
|
-
var parseMessageFile = async (filePath,
|
|
203
|
-
const
|
|
88
|
+
var parseMessageFile = async (filePath, loggerOptions) => {
|
|
89
|
+
const baseLogger = getLogger({ ...loggerOptions });
|
|
90
|
+
const logger = baseLogger.child({ scope: "parse-message-file" });
|
|
204
91
|
const trimmedPath = filePath.trim();
|
|
205
92
|
if (!trimmedPath) {
|
|
206
|
-
logger
|
|
93
|
+
logger.warn("File path is empty.", { filePath: trimmedPath });
|
|
207
94
|
return null;
|
|
208
95
|
}
|
|
209
96
|
if (trimmedPath.length > MAX_PATH_LENGTH) {
|
|
210
|
-
logger
|
|
97
|
+
logger.warn("File path exceeds maximum length.", { filePath: trimmedPath });
|
|
211
98
|
return null;
|
|
212
99
|
}
|
|
213
100
|
const fileName = path.basename(trimmedPath);
|
|
214
101
|
if (!fileName.toLowerCase().endsWith(".json")) {
|
|
215
|
-
logger
|
|
102
|
+
logger.trace("Skipped non-JSON file.", { filePath: trimmedPath });
|
|
216
103
|
return null;
|
|
217
104
|
}
|
|
218
105
|
try {
|
|
219
|
-
const { content } = await readMessageRecordFile(trimmedPath);
|
|
220
|
-
logger
|
|
106
|
+
const { content } = await readMessageRecordFile(trimmedPath, loggerOptions);
|
|
107
|
+
logger.trace(`Message file loaded.`, { filePath: trimmedPath });
|
|
221
108
|
return content;
|
|
222
109
|
} catch (error) {
|
|
223
|
-
logger
|
|
110
|
+
logger.warn("Failed to parse message file.", {
|
|
111
|
+
filePath: trimmedPath,
|
|
112
|
+
error
|
|
113
|
+
});
|
|
224
114
|
return null;
|
|
225
115
|
}
|
|
226
116
|
};
|
|
227
117
|
|
|
228
|
-
// src/modules/
|
|
229
|
-
var mergeNamespaceMessages = async (filePaths, isAtRoot,
|
|
118
|
+
// src/modules/messages/load-local-messages/load-namespace-group/merge-namespace-messages.ts
|
|
119
|
+
var mergeNamespaceMessages = async (filePaths, isAtRoot, loggerOptions) => {
|
|
230
120
|
const baseContent = {};
|
|
231
121
|
const subEntries = {};
|
|
232
122
|
await Promise.all(
|
|
233
123
|
filePaths.map(async (filePath) => {
|
|
234
124
|
const fileName = path.basename(filePath);
|
|
235
|
-
const content = await parseMessageFile(filePath,
|
|
125
|
+
const content = await parseMessageFile(filePath, loggerOptions);
|
|
236
126
|
if (!content) {
|
|
237
127
|
return;
|
|
238
128
|
}
|
|
@@ -247,20 +137,21 @@ var mergeNamespaceMessages = async (filePaths, isAtRoot, loggerId) => {
|
|
|
247
137
|
return { base: baseContent, sub: subEntries };
|
|
248
138
|
};
|
|
249
139
|
|
|
250
|
-
// src/modules/
|
|
140
|
+
// src/modules/messages/load-local-messages/load-namespace-group/load-namespace-group.ts
|
|
251
141
|
var loadNamespaceGroup = async ({
|
|
252
142
|
locale,
|
|
253
143
|
namespace,
|
|
254
144
|
messages,
|
|
255
145
|
namespaceGroupValue,
|
|
256
146
|
limit,
|
|
257
|
-
|
|
147
|
+
logger: loggerOptions = { id: "default" }
|
|
258
148
|
}) => {
|
|
259
|
-
const
|
|
149
|
+
const baseLogger = getLogger({ ...loggerOptions });
|
|
150
|
+
const logger = baseLogger.child({ scope: "load-namespace-group" });
|
|
260
151
|
const { isAtRoot, filePaths } = namespaceGroupValue;
|
|
261
152
|
if (filePaths.length === 0) {
|
|
262
|
-
logger
|
|
263
|
-
`Skipped merging
|
|
153
|
+
logger.trace(
|
|
154
|
+
`Skipped merging ${locale}/${namespace} because filePaths is empty`
|
|
264
155
|
);
|
|
265
156
|
return;
|
|
266
157
|
}
|
|
@@ -268,15 +159,19 @@ var loadNamespaceGroup = async ({
|
|
|
268
159
|
const { base, sub } = await mergeNamespaceMessages(
|
|
269
160
|
filePaths,
|
|
270
161
|
isAtRoot,
|
|
271
|
-
|
|
162
|
+
loggerOptions
|
|
272
163
|
);
|
|
273
164
|
if (!messages[locale]) {
|
|
274
165
|
messages[locale] = {};
|
|
275
166
|
}
|
|
167
|
+
if (isAtRoot && filePaths.length === 1 && path.basename(filePaths[0]) === "index.json") {
|
|
168
|
+
messages[locale] = { ...messages[locale], ...base };
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
276
171
|
const finalContent = isAtRoot ? base : { ...base, ...sub };
|
|
277
172
|
messages[locale][namespace] = finalContent;
|
|
278
173
|
if (!isAtRoot && Object.keys(finalContent).length > 0) {
|
|
279
|
-
logger
|
|
174
|
+
logger.trace(
|
|
280
175
|
`Merged ${locale}/${namespace} from ${filePaths.length} file(s)`,
|
|
281
176
|
{ namespace }
|
|
282
177
|
);
|
|
@@ -309,15 +204,17 @@ var addToNamespaceGroup = ({
|
|
|
309
204
|
}
|
|
310
205
|
};
|
|
311
206
|
|
|
312
|
-
// src/modules/
|
|
207
|
+
// src/modules/messages/load-local-messages/prepare-namespace-groups/traverse-directory.ts
|
|
313
208
|
var traverseDirectory = async ({
|
|
314
209
|
options,
|
|
315
210
|
currentDirPath,
|
|
316
211
|
namespaceGroups,
|
|
317
212
|
namespacePathSegments
|
|
318
213
|
}) => {
|
|
319
|
-
const { limit
|
|
320
|
-
const
|
|
214
|
+
const { limit } = options;
|
|
215
|
+
const loggerOptions = options.logger || { id: "default" };
|
|
216
|
+
const baseLogger = getLogger({ ...loggerOptions });
|
|
217
|
+
const logger = baseLogger.child({ scope: "traverse-directory" });
|
|
321
218
|
try {
|
|
322
219
|
const dirents = await fs.readdir(currentDirPath, { withFileTypes: true });
|
|
323
220
|
const dirPromises = dirents.map(
|
|
@@ -353,7 +250,7 @@ var traverseDirectory = async ({
|
|
|
353
250
|
}
|
|
354
251
|
};
|
|
355
252
|
|
|
356
|
-
// src/modules/
|
|
253
|
+
// src/modules/messages/load-local-messages/prepare-namespace-groups/prepare-namespace-groups.ts
|
|
357
254
|
var prepareNamespaceGroups = async (options) => {
|
|
358
255
|
const { basePath } = options;
|
|
359
256
|
const namespaceGroups = /* @__PURE__ */ new Map();
|
|
@@ -366,16 +263,17 @@ var prepareNamespaceGroups = async (options) => {
|
|
|
366
263
|
return namespaceGroups;
|
|
367
264
|
};
|
|
368
265
|
|
|
369
|
-
// src/modules/
|
|
266
|
+
// src/modules/messages/load-local-messages/load-single-locale/load-single-locale.ts
|
|
370
267
|
var loadSingleLocale = async ({
|
|
371
268
|
basePath,
|
|
372
269
|
locale,
|
|
373
270
|
namespaces,
|
|
374
271
|
messages,
|
|
375
272
|
limit,
|
|
376
|
-
|
|
273
|
+
logger: loggerOptions = { id: "default" }
|
|
377
274
|
}) => {
|
|
378
|
-
const
|
|
275
|
+
const baseLogger = getLogger({ ...loggerOptions });
|
|
276
|
+
const logger = baseLogger.child({ scope: "load-single-locale" });
|
|
379
277
|
const localePath = path.join(basePath, locale);
|
|
380
278
|
const validNamespaces = [];
|
|
381
279
|
try {
|
|
@@ -388,14 +286,14 @@ var loadSingleLocale = async ({
|
|
|
388
286
|
return;
|
|
389
287
|
}
|
|
390
288
|
} catch (error) {
|
|
391
|
-
logger.warn("Error checking locale path
|
|
289
|
+
logger.warn("Error checking locale path.", { locale, error });
|
|
392
290
|
return;
|
|
393
291
|
}
|
|
394
292
|
const namespaceGroups = await prepareNamespaceGroups({
|
|
395
293
|
basePath: localePath,
|
|
396
294
|
limit,
|
|
397
295
|
namespaces: new Set(namespaces || []),
|
|
398
|
-
|
|
296
|
+
logger: loggerOptions
|
|
399
297
|
});
|
|
400
298
|
if (namespaceGroups.size === 0) {
|
|
401
299
|
logger.warn("No namespace groups found.", {
|
|
@@ -405,7 +303,7 @@ var loadSingleLocale = async ({
|
|
|
405
303
|
});
|
|
406
304
|
return;
|
|
407
305
|
}
|
|
408
|
-
logger.
|
|
306
|
+
logger.trace("Prepared namespace groups from scanning local files.", {
|
|
409
307
|
namespaceGroups: [...namespaceGroups.entries()].map(([ns, val]) => ({
|
|
410
308
|
namespace: ns,
|
|
411
309
|
isAtRoot: val.isAtRoot,
|
|
@@ -421,14 +319,14 @@ var loadSingleLocale = async ({
|
|
|
421
319
|
messages,
|
|
422
320
|
namespaceGroupValue,
|
|
423
321
|
limit,
|
|
424
|
-
|
|
322
|
+
logger: loggerOptions
|
|
425
323
|
}).then(() => validNamespaces.push(namespace))
|
|
426
324
|
);
|
|
427
325
|
await Promise.all(namespaceGroupTasks);
|
|
428
326
|
return validNamespaces;
|
|
429
327
|
};
|
|
430
328
|
|
|
431
|
-
// src/modules/
|
|
329
|
+
// src/modules/messages/load-local-messages/load-locale-with-fallback/load-locale-with-fallback.ts
|
|
432
330
|
var loadLocaleWithFallback = async ({
|
|
433
331
|
basePath,
|
|
434
332
|
locale: targetLocale,
|
|
@@ -436,9 +334,10 @@ var loadLocaleWithFallback = async ({
|
|
|
436
334
|
namespaces,
|
|
437
335
|
messages,
|
|
438
336
|
limit,
|
|
439
|
-
|
|
337
|
+
logger: loggerOptions = { id: "default" }
|
|
440
338
|
}) => {
|
|
441
|
-
const
|
|
339
|
+
const baseLogger = getLogger({ ...loggerOptions });
|
|
340
|
+
const logger = baseLogger.child({ scope: "load-locale-with-fallback" });
|
|
442
341
|
const localesToTry = [targetLocale, ...fallbackLocales];
|
|
443
342
|
for (const locale of localesToTry) {
|
|
444
343
|
try {
|
|
@@ -448,7 +347,7 @@ var loadLocaleWithFallback = async ({
|
|
|
448
347
|
namespaces,
|
|
449
348
|
messages,
|
|
450
349
|
limit,
|
|
451
|
-
|
|
350
|
+
logger: loggerOptions
|
|
452
351
|
});
|
|
453
352
|
return validNamespaces;
|
|
454
353
|
} catch (error) {
|
|
@@ -461,14 +360,155 @@ var loadLocaleWithFallback = async ({
|
|
|
461
360
|
logger.warn("All fallback locales failed.", {
|
|
462
361
|
attemptedLocales: localesToTry
|
|
463
362
|
});
|
|
464
|
-
return
|
|
363
|
+
return;
|
|
364
|
+
};
|
|
365
|
+
function getGlobalMessagesPool() {
|
|
366
|
+
if (!globalThis.__INTOR_MESSAGES_POOL__) {
|
|
367
|
+
globalThis.__INTOR_MESSAGES_POOL__ = new Keyv();
|
|
368
|
+
}
|
|
369
|
+
return globalThis.__INTOR_MESSAGES_POOL__;
|
|
370
|
+
}
|
|
371
|
+
function clearMessagesPool() {
|
|
372
|
+
const pool = getGlobalMessagesPool();
|
|
373
|
+
pool.clear();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// src/shared/utils/merge-messages.ts
|
|
377
|
+
var mergeMessages = (staticMessages = {}, loadedMessages = {}) => {
|
|
378
|
+
const result = Object.keys(staticMessages).length ? { ...staticMessages } : {};
|
|
379
|
+
for (const locale in loadedMessages) {
|
|
380
|
+
const loaded = loadedMessages[locale];
|
|
381
|
+
if (!result[locale]) {
|
|
382
|
+
result[locale] = loaded;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
result[locale] = {
|
|
386
|
+
...result[locale],
|
|
387
|
+
...loaded
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
return result;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// src/shared/utils/normalize-cache-key.ts
|
|
394
|
+
var CACHE_KEY_DELIMITER = "|";
|
|
395
|
+
var sanitize = (k) => k.replaceAll(/[\u200B-\u200D\uFEFF]/g, "").replaceAll(/[\r\n]/g, "").trim();
|
|
396
|
+
var normalizeCacheKey = (key, delimiter = CACHE_KEY_DELIMITER) => {
|
|
397
|
+
if (!key) return null;
|
|
398
|
+
if (Array.isArray(key)) {
|
|
399
|
+
if (key.length === 0) return null;
|
|
400
|
+
const normalized = key.map((k) => {
|
|
401
|
+
if (k === null) return "__null";
|
|
402
|
+
if (k === void 0) return "__undefined";
|
|
403
|
+
if (typeof k === "boolean") return k ? "__true" : "__false";
|
|
404
|
+
return sanitize(String(k));
|
|
405
|
+
});
|
|
406
|
+
return normalized.join(delimiter);
|
|
407
|
+
}
|
|
408
|
+
if (typeof key === "boolean") return key ? "__true" : "__false";
|
|
409
|
+
return String(key);
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
// src/shared/constants/prefix-placeholder.ts
|
|
413
|
+
var PREFIX_PLACEHOLDER = "{locale}";
|
|
414
|
+
|
|
415
|
+
// src/shared/utils/resolve-namespaces.ts
|
|
416
|
+
var resolveNamespaces = ({
|
|
417
|
+
config,
|
|
418
|
+
pathname
|
|
419
|
+
}) => {
|
|
420
|
+
const { loader } = config;
|
|
421
|
+
const {
|
|
422
|
+
routeNamespaces = {},
|
|
423
|
+
namespaces: fallbackNamespaces
|
|
424
|
+
} = loader;
|
|
425
|
+
const { unprefixedPathname } = extractPathname({ config, pathname });
|
|
426
|
+
const standardizedPathname = standardizePathname({
|
|
427
|
+
config,
|
|
428
|
+
pathname: unprefixedPathname
|
|
429
|
+
});
|
|
430
|
+
const placeholderRemovedPathname = standardizedPathname.replace(
|
|
431
|
+
`/${PREFIX_PLACEHOLDER}`,
|
|
432
|
+
""
|
|
433
|
+
);
|
|
434
|
+
const defaultNamespaces = routeNamespaces.default ?? [];
|
|
435
|
+
const exactMatchNamespaces = routeNamespaces[standardizedPathname] ?? routeNamespaces[placeholderRemovedPathname];
|
|
436
|
+
if (exactMatchNamespaces) {
|
|
437
|
+
return [...defaultNamespaces, ...exactMatchNamespaces];
|
|
438
|
+
}
|
|
439
|
+
let bestMatch = "";
|
|
440
|
+
let bestNamespaces;
|
|
441
|
+
const prefixPatterns = Object.keys(routeNamespaces).filter(
|
|
442
|
+
(pattern) => pattern.endsWith("/*")
|
|
443
|
+
);
|
|
444
|
+
for (const pattern of prefixPatterns) {
|
|
445
|
+
const basePath = pattern.replace(/\/\*$/, "");
|
|
446
|
+
if (standardizedPathname.startsWith(basePath)) {
|
|
447
|
+
if (basePath.length > bestMatch.length) {
|
|
448
|
+
bestMatch = basePath;
|
|
449
|
+
bestNamespaces = routeNamespaces[pattern];
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const matchedNamespaces = bestNamespaces ?? routeNamespaces["/*"] ?? fallbackNamespaces ?? [];
|
|
454
|
+
if (matchedNamespaces.length > 0) {
|
|
455
|
+
return [...defaultNamespaces, ...matchedNamespaces];
|
|
456
|
+
} else {
|
|
457
|
+
return [...defaultNamespaces];
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
// src/shared/utils/locale/normalize-locale.ts
|
|
462
|
+
var normalizeLocale = (locale = "", supportedLocales = []) => {
|
|
463
|
+
if (!locale || supportedLocales.length === 0) return;
|
|
464
|
+
const toCanonical = (input) => {
|
|
465
|
+
try {
|
|
466
|
+
return Intl.getCanonicalLocales(input)[0]?.toLowerCase();
|
|
467
|
+
} catch {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
const canonicalLocale = toCanonical(locale);
|
|
472
|
+
if (!canonicalLocale) return;
|
|
473
|
+
const supportedCanonicalMap = /* @__PURE__ */ new Map();
|
|
474
|
+
for (const l of supportedLocales) {
|
|
475
|
+
const normalized = toCanonical(l);
|
|
476
|
+
if (normalized) {
|
|
477
|
+
supportedCanonicalMap.set(normalized, l);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (supportedCanonicalMap.has(canonicalLocale)) {
|
|
481
|
+
return supportedCanonicalMap.get(canonicalLocale);
|
|
482
|
+
}
|
|
483
|
+
const baseLang = canonicalLocale.split("-")[0];
|
|
484
|
+
for (const [key, original] of supportedCanonicalMap) {
|
|
485
|
+
const supportedBase = key.split("-")[0];
|
|
486
|
+
if (supportedBase === baseLang) {
|
|
487
|
+
return original;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return;
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
// src/shared/utils/locale/resolve-preferred-locale.ts
|
|
494
|
+
var resolvePreferredLocale = (acceptLanguageHeader, supportedLocales) => {
|
|
495
|
+
if (!acceptLanguageHeader || !supportedLocales || supportedLocales.length === 0) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const supportedLocalesSet = new Set(supportedLocales);
|
|
499
|
+
const preferred = acceptLanguageHeader.split(",").map((part) => {
|
|
500
|
+
const [lang, qValue] = part.split(";");
|
|
501
|
+
const q = qValue ? parseFloat(qValue.split("=")[1]) : 1;
|
|
502
|
+
if (isNaN(q)) {
|
|
503
|
+
return { lang: lang.trim(), q: 0 };
|
|
504
|
+
}
|
|
505
|
+
return { lang: lang.trim(), q };
|
|
506
|
+
}).sort((a, b) => b.q - a.q).find(({ lang }) => supportedLocalesSet.has(lang))?.lang;
|
|
507
|
+
return preferred;
|
|
465
508
|
};
|
|
466
509
|
|
|
467
510
|
// src/shared/utils/pathname/normalize-pathname.ts
|
|
468
511
|
var normalizePathname = (rawPathname, options = {}) => {
|
|
469
|
-
if (typeof rawPathname !== "string") {
|
|
470
|
-
throw new TypeError("Expected rawPathname to be a string");
|
|
471
|
-
}
|
|
472
512
|
const length = rawPathname.length;
|
|
473
513
|
let start = 0;
|
|
474
514
|
let end = length - 1;
|
|
@@ -498,33 +538,101 @@ var normalizePathname = (rawPathname, options = {}) => {
|
|
|
498
538
|
return result || "/";
|
|
499
539
|
};
|
|
500
540
|
|
|
501
|
-
// src/
|
|
541
|
+
// src/shared/utils/pathname/extract-pathname.ts
|
|
542
|
+
var extractPathname = ({
|
|
543
|
+
config,
|
|
544
|
+
pathname: rawPathname
|
|
545
|
+
}) => {
|
|
546
|
+
const { routing, defaultLocale } = config;
|
|
547
|
+
const { basePath, prefix } = routing;
|
|
548
|
+
const normalizedPathname = normalizePathname(rawPathname);
|
|
549
|
+
let prefixedPathname = normalizedPathname;
|
|
550
|
+
if (basePath && normalizedPathname.startsWith(basePath + "/")) {
|
|
551
|
+
prefixedPathname = normalizedPathname.slice(basePath.length) || "/";
|
|
552
|
+
} else if (basePath && normalizedPathname === basePath) {
|
|
553
|
+
prefixedPathname = "/";
|
|
554
|
+
}
|
|
555
|
+
const pathParts = prefixedPathname.split("/").filter(Boolean);
|
|
556
|
+
const maybeLocale = pathParts[0] || "";
|
|
557
|
+
const isLocalePrefixed = config.supportedLocales?.includes(maybeLocale);
|
|
558
|
+
let unprefixedPathname = prefixedPathname;
|
|
559
|
+
if (prefix === "all") {
|
|
560
|
+
if (isLocalePrefixed) {
|
|
561
|
+
unprefixedPathname = prefixedPathname.slice(maybeLocale.length + 1) || "/";
|
|
562
|
+
}
|
|
563
|
+
} else if (prefix === "except-default") {
|
|
564
|
+
if (maybeLocale && maybeLocale !== defaultLocale && isLocalePrefixed) {
|
|
565
|
+
unprefixedPathname = prefixedPathname.slice(maybeLocale.length + 1) || "/";
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return {
|
|
569
|
+
basePath,
|
|
570
|
+
prefixedPathname,
|
|
571
|
+
unprefixedPathname,
|
|
572
|
+
maybeLocale,
|
|
573
|
+
isLocalePrefixed: Boolean(isLocalePrefixed)
|
|
574
|
+
};
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
// src/shared/utils/pathname/standardize-pathname.ts
|
|
578
|
+
var standardizePathname = ({
|
|
579
|
+
config,
|
|
580
|
+
pathname
|
|
581
|
+
}) => {
|
|
582
|
+
const { routing } = config;
|
|
583
|
+
const { basePath } = routing;
|
|
584
|
+
const parts = [
|
|
585
|
+
normalizePathname(basePath),
|
|
586
|
+
PREFIX_PLACEHOLDER,
|
|
587
|
+
normalizePathname(pathname)
|
|
588
|
+
];
|
|
589
|
+
const standardizedPathname = parts.join("/").replace(/\/{2,}/g, "/");
|
|
590
|
+
return normalizePathname(standardizedPathname);
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
// src/modules/messages/load-local-messages/load-local-messages.ts
|
|
502
594
|
var loadLocalMessages = async ({
|
|
503
|
-
basePath
|
|
595
|
+
basePath,
|
|
504
596
|
locale,
|
|
505
597
|
fallbackLocales,
|
|
506
598
|
namespaces,
|
|
507
599
|
concurrency = 10,
|
|
508
|
-
|
|
600
|
+
cache = DEFAULT_CACHE_OPTIONS,
|
|
601
|
+
logger: loggerOptions = { id: "default" }
|
|
509
602
|
}) => {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
const logger =
|
|
603
|
+
basePath = basePath ?? "messages";
|
|
604
|
+
if (!locale || locale.trim() === "") return {};
|
|
605
|
+
const baseLogger = getLogger({ ...loggerOptions });
|
|
606
|
+
const logger = baseLogger.child({ scope: "load-locale-messages" });
|
|
607
|
+
const messages = {};
|
|
514
608
|
const resolvedBasePath = path.resolve(
|
|
515
609
|
process.cwd(),
|
|
516
610
|
normalizePathname(basePath, { removeLeadingSlash: true })
|
|
517
611
|
);
|
|
518
|
-
const messages = {};
|
|
519
|
-
const limit = pLimit(concurrency);
|
|
520
612
|
const start = performance.now();
|
|
521
|
-
logger.
|
|
613
|
+
logger.trace("Starting to load local messages with configuration.", {
|
|
522
614
|
path: { basePath, resolvedBasePath },
|
|
523
615
|
locale,
|
|
524
616
|
fallbackLocales,
|
|
525
|
-
namespaces: namespaces && namespaces.length > 0 ? { count: namespaces?.length, list: [...namespaces] } : "All",
|
|
617
|
+
namespaces: namespaces && namespaces.length > 0 ? { count: namespaces?.length, list: [...namespaces] } : "All Namespaces",
|
|
526
618
|
concurrency
|
|
527
619
|
});
|
|
620
|
+
const pool = getGlobalMessagesPool();
|
|
621
|
+
const key = normalizeCacheKey([
|
|
622
|
+
loggerOptions.id,
|
|
623
|
+
resolvedBasePath,
|
|
624
|
+
locale,
|
|
625
|
+
[...fallbackLocales ?? []].sort().join(","),
|
|
626
|
+
[...namespaces ?? []].sort().join(",")
|
|
627
|
+
]);
|
|
628
|
+
if (cache.enabled && key) {
|
|
629
|
+
const cached = await pool.get(key);
|
|
630
|
+
if (cached) {
|
|
631
|
+
logger.debug("Messages cache hit.", { key });
|
|
632
|
+
return cached;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
const limit = pLimit(concurrency);
|
|
528
636
|
const validNamespaces = await loadLocaleWithFallback({
|
|
529
637
|
basePath: resolvedBasePath,
|
|
530
638
|
locale,
|
|
@@ -532,431 +640,261 @@ var loadLocalMessages = async ({
|
|
|
532
640
|
namespaces,
|
|
533
641
|
messages,
|
|
534
642
|
limit,
|
|
535
|
-
|
|
643
|
+
logger: loggerOptions
|
|
536
644
|
});
|
|
645
|
+
if (cache.enabled && key) {
|
|
646
|
+
await pool.set(key, messages, cache.ttl);
|
|
647
|
+
}
|
|
537
648
|
const end = performance.now();
|
|
538
649
|
const duration = Math.round(end - start);
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
});
|
|
545
|
-
} else {
|
|
546
|
-
logger.warn("No valid namespaces found for the locale.", {
|
|
547
|
-
locale,
|
|
548
|
-
namespaces: validNamespaces,
|
|
549
|
-
duration: `${duration} ms`
|
|
550
|
-
});
|
|
551
|
-
}
|
|
650
|
+
logger.trace("Finished loading local messages.", {
|
|
651
|
+
locale,
|
|
652
|
+
validNamespaces,
|
|
653
|
+
duration: `${duration} ms`
|
|
654
|
+
});
|
|
552
655
|
return messages;
|
|
553
656
|
};
|
|
554
657
|
|
|
555
|
-
// src/modules/
|
|
556
|
-
var
|
|
658
|
+
// src/modules/messages/create-load-local-messages.ts
|
|
659
|
+
var createLoadLocalMessages = (basePath) => {
|
|
557
660
|
return (options) => loadLocalMessages({ basePath, ...options });
|
|
558
661
|
};
|
|
559
662
|
|
|
560
|
-
// src/modules/
|
|
561
|
-
var
|
|
562
|
-
const searchParams = new URLSearchParams();
|
|
563
|
-
const appendParam = (key, value) => {
|
|
564
|
-
if (value === void 0 || value === null) {
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
if (Array.isArray(value) && value.length > 0) {
|
|
568
|
-
value.forEach((v) => v && searchParams.append(key, v));
|
|
569
|
-
} else {
|
|
570
|
-
searchParams.append(key, value);
|
|
571
|
-
}
|
|
572
|
-
};
|
|
573
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
574
|
-
appendParam(key, value);
|
|
575
|
-
});
|
|
576
|
-
return searchParams;
|
|
577
|
-
};
|
|
578
|
-
var fetcher = async ({
|
|
663
|
+
// src/modules/messages/load-api-messages/fetch-messages.ts
|
|
664
|
+
var fetchMessages = async ({
|
|
579
665
|
apiUrl,
|
|
666
|
+
apiHeaders,
|
|
580
667
|
locale,
|
|
581
668
|
searchParams,
|
|
582
|
-
|
|
669
|
+
logger: loggerOptions = { id: "default" }
|
|
583
670
|
}) => {
|
|
584
|
-
const
|
|
671
|
+
const baseLogger = getLogger({ ...loggerOptions });
|
|
672
|
+
const logger = baseLogger.child({ scope: "fetch-messages" });
|
|
585
673
|
try {
|
|
586
674
|
const params = new URLSearchParams(searchParams);
|
|
587
675
|
params.append("locale", locale);
|
|
588
676
|
const url = `${apiUrl}?${params.toString()}`;
|
|
677
|
+
const headers = {
|
|
678
|
+
"Content-Type": "application/json",
|
|
679
|
+
...apiHeaders
|
|
680
|
+
};
|
|
589
681
|
const response = await fetch(url, {
|
|
590
682
|
method: "GET",
|
|
591
|
-
headers
|
|
683
|
+
headers,
|
|
592
684
|
cache: "no-store"
|
|
593
685
|
});
|
|
594
686
|
if (!response.ok) {
|
|
595
|
-
throw new Error(
|
|
596
|
-
`Fetch failed for locale "${locale}" at URL: ${url} - ${response.status} ${response.statusText}`
|
|
597
|
-
);
|
|
687
|
+
throw new Error(`Fetch failed: ${locale} (${response.status})`);
|
|
598
688
|
}
|
|
599
689
|
const data = await response.json();
|
|
600
690
|
if (data == null || typeof data === "object" && Object.keys(data).length === 0) {
|
|
601
|
-
throw new Error(
|
|
602
|
-
`Missing or invalid messages for locale "${locale}" at URL: ${url}`
|
|
603
|
-
);
|
|
691
|
+
throw new Error(`Invalid messages: ${locale}`);
|
|
604
692
|
}
|
|
605
693
|
return data;
|
|
606
|
-
} catch {
|
|
694
|
+
} catch (error) {
|
|
607
695
|
logger.warn(`Failed to fetch messages for locale "${locale}".`, {
|
|
608
696
|
locale,
|
|
609
697
|
apiUrl,
|
|
610
|
-
searchParams: decodeURIComponent(searchParams.toString())
|
|
698
|
+
searchParams: decodeURIComponent(searchParams.toString()),
|
|
699
|
+
error
|
|
611
700
|
});
|
|
612
|
-
return
|
|
701
|
+
return;
|
|
613
702
|
}
|
|
614
703
|
};
|
|
615
704
|
|
|
616
|
-
// src/modules/
|
|
617
|
-
var fetchFallbackMessages = async (
|
|
705
|
+
// src/modules/messages/load-api-messages/fetch-fallback-messages.ts
|
|
706
|
+
var fetchFallbackMessages = async ({
|
|
707
|
+
apiUrl,
|
|
708
|
+
apiHeaders,
|
|
709
|
+
searchParams,
|
|
710
|
+
fallbackLocales,
|
|
711
|
+
logger
|
|
712
|
+
}) => {
|
|
618
713
|
for (const fallbackLocale of fallbackLocales) {
|
|
619
|
-
const result = await
|
|
714
|
+
const result = await fetchMessages({
|
|
620
715
|
apiUrl,
|
|
621
716
|
searchParams,
|
|
622
717
|
locale: fallbackLocale,
|
|
623
|
-
|
|
718
|
+
apiHeaders,
|
|
719
|
+
logger
|
|
624
720
|
});
|
|
625
721
|
if (result) {
|
|
626
722
|
return { locale: fallbackLocale, messages: result };
|
|
627
723
|
}
|
|
628
724
|
}
|
|
629
|
-
return
|
|
725
|
+
return;
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
// src/modules/messages/load-api-messages/utils/build-search-params.ts
|
|
729
|
+
var buildSearchParams = (params) => {
|
|
730
|
+
const searchParams = new URLSearchParams();
|
|
731
|
+
const appendParam = (key, value) => {
|
|
732
|
+
if (value === void 0 || value === null) return;
|
|
733
|
+
if (Array.isArray(value) && value.length === 0) return;
|
|
734
|
+
if (Array.isArray(value)) {
|
|
735
|
+
value.forEach((v) => v && searchParams.append(key, v));
|
|
736
|
+
} else {
|
|
737
|
+
searchParams.append(key, value);
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
741
|
+
appendParam(key, value);
|
|
742
|
+
});
|
|
743
|
+
return searchParams;
|
|
630
744
|
};
|
|
631
745
|
|
|
632
|
-
// src/modules/
|
|
633
|
-
var
|
|
746
|
+
// src/modules/messages/load-api-messages/load-api-messages.ts
|
|
747
|
+
var loadApiMessages = async ({
|
|
634
748
|
apiUrl,
|
|
749
|
+
apiHeaders,
|
|
635
750
|
basePath,
|
|
636
751
|
locale,
|
|
637
752
|
fallbackLocales = [],
|
|
638
753
|
namespaces = [],
|
|
639
|
-
|
|
754
|
+
cache = DEFAULT_CACHE_OPTIONS,
|
|
755
|
+
logger: loggerOptions = { id: "default" }
|
|
640
756
|
}) => {
|
|
641
|
-
const
|
|
757
|
+
const baseLogger = getLogger({ ...loggerOptions });
|
|
758
|
+
const logger = baseLogger.child({ scope: "load-api-messages" });
|
|
642
759
|
if (!apiUrl) {
|
|
643
|
-
logger.warn("No apiUrl provided
|
|
644
|
-
return
|
|
760
|
+
logger.warn("No apiUrl provided. Skipping fetch.");
|
|
761
|
+
return;
|
|
645
762
|
}
|
|
646
|
-
const
|
|
763
|
+
const pool = getGlobalMessagesPool();
|
|
764
|
+
const key = normalizeCacheKey([
|
|
765
|
+
loggerOptions.id,
|
|
647
766
|
basePath,
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
767
|
+
locale,
|
|
768
|
+
[...fallbackLocales ?? []].sort().join(","),
|
|
769
|
+
[...namespaces ?? []].sort().join(",")
|
|
770
|
+
]);
|
|
771
|
+
if (cache.enabled && key) {
|
|
772
|
+
const cached = await pool.get(key);
|
|
773
|
+
if (cached) {
|
|
774
|
+
logger.debug("Messages cache hit.", { key });
|
|
775
|
+
return cached;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
const searchParams = buildSearchParams({ basePath, namespaces });
|
|
779
|
+
const messages = await fetchMessages({
|
|
652
780
|
apiUrl,
|
|
781
|
+
apiHeaders,
|
|
653
782
|
searchParams,
|
|
654
783
|
locale,
|
|
655
|
-
|
|
784
|
+
logger: loggerOptions
|
|
656
785
|
});
|
|
657
786
|
if (messages) {
|
|
787
|
+
if (cache.enabled && key) {
|
|
788
|
+
await pool.set(key, messages, cache.ttl);
|
|
789
|
+
}
|
|
658
790
|
return messages;
|
|
659
791
|
}
|
|
660
|
-
const fallbackResult = await fetchFallbackMessages(
|
|
792
|
+
const fallbackResult = await fetchFallbackMessages({
|
|
661
793
|
apiUrl,
|
|
794
|
+
apiHeaders,
|
|
662
795
|
searchParams,
|
|
663
796
|
fallbackLocales,
|
|
664
|
-
|
|
665
|
-
);
|
|
797
|
+
logger: loggerOptions
|
|
798
|
+
});
|
|
666
799
|
if (fallbackResult) {
|
|
667
800
|
logger.info("Fallback locale succeeded.", {
|
|
668
801
|
usedLocale: fallbackResult.locale,
|
|
669
802
|
apiUrl,
|
|
670
803
|
searchParams: decodeURIComponent(searchParams.toString())
|
|
671
804
|
});
|
|
805
|
+
if (cache.enabled && key) {
|
|
806
|
+
await pool.set(key, fallbackResult.messages, cache.ttl);
|
|
807
|
+
}
|
|
672
808
|
return fallbackResult.messages;
|
|
673
809
|
}
|
|
674
810
|
logger.warn("Failed to fetch messages for all locales.", {
|
|
675
811
|
locale,
|
|
676
812
|
fallbackLocales
|
|
677
813
|
});
|
|
678
|
-
return
|
|
814
|
+
return;
|
|
679
815
|
};
|
|
680
816
|
|
|
681
|
-
// src/
|
|
682
|
-
var
|
|
683
|
-
config,
|
|
684
|
-
pathname: rawPathname
|
|
685
|
-
}) => {
|
|
686
|
-
const { routing, defaultLocale } = config;
|
|
687
|
-
const { basePath, prefix } = routing;
|
|
688
|
-
const normalizedPathname = normalizePathname(rawPathname);
|
|
689
|
-
let prefixedPathname = normalizedPathname;
|
|
690
|
-
if (basePath && normalizedPathname.startsWith(basePath + "/")) {
|
|
691
|
-
prefixedPathname = normalizedPathname.slice(basePath.length) || "/";
|
|
692
|
-
} else if (basePath && normalizedPathname === basePath) {
|
|
693
|
-
prefixedPathname = "/";
|
|
694
|
-
}
|
|
695
|
-
const pathParts = prefixedPathname.split("/").filter(Boolean);
|
|
696
|
-
const maybeLocale = pathParts[0] || "";
|
|
697
|
-
const isLocalePrefixed = config.supportedLocales?.includes(maybeLocale);
|
|
698
|
-
let unprefixedPathname = prefixedPathname;
|
|
699
|
-
if (prefix === "all") {
|
|
700
|
-
if (isLocalePrefixed) {
|
|
701
|
-
unprefixedPathname = prefixedPathname.slice(maybeLocale.length + 1) || "/";
|
|
702
|
-
}
|
|
703
|
-
} else if (prefix === "except-default") {
|
|
704
|
-
if (maybeLocale && maybeLocale !== defaultLocale && isLocalePrefixed) {
|
|
705
|
-
unprefixedPathname = prefixedPathname.slice(maybeLocale.length + 1) || "/";
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
return {
|
|
709
|
-
basePath,
|
|
710
|
-
prefixedPathname,
|
|
711
|
-
unprefixedPathname,
|
|
712
|
-
maybeLocale,
|
|
713
|
-
isLocalePrefixed: Boolean(isLocalePrefixed)
|
|
714
|
-
};
|
|
715
|
-
};
|
|
716
|
-
|
|
717
|
-
// src/shared/utils/pathname/standardize-pathname.ts
|
|
718
|
-
var standardizePathname = ({
|
|
719
|
-
config,
|
|
720
|
-
pathname
|
|
721
|
-
}) => {
|
|
722
|
-
const { routing, prefixPlaceHolder } = config;
|
|
723
|
-
const { basePath } = routing;
|
|
724
|
-
const parts = [
|
|
725
|
-
normalizePathname(basePath),
|
|
726
|
-
normalizePathname(prefixPlaceHolder),
|
|
727
|
-
normalizePathname(pathname)
|
|
728
|
-
];
|
|
729
|
-
const standardizedPathname = parts.join("/").replace(/\/{2,}/g, "/");
|
|
730
|
-
return normalizePathname(standardizedPathname);
|
|
731
|
-
};
|
|
732
|
-
|
|
733
|
-
// src/shared/utils/resolve-namespaces.ts
|
|
734
|
-
var resolveNamespaces = ({
|
|
735
|
-
config,
|
|
736
|
-
pathname
|
|
737
|
-
}) => {
|
|
738
|
-
const { loaderOptions, prefixPlaceHolder } = config;
|
|
739
|
-
const {
|
|
740
|
-
routeNamespaces = {},
|
|
741
|
-
namespaces: fallbackNamespaces
|
|
742
|
-
} = loaderOptions;
|
|
743
|
-
const { unprefixedPathname } = extractPathname({ config, pathname });
|
|
744
|
-
const standardizedPathname = standardizePathname({
|
|
745
|
-
config,
|
|
746
|
-
pathname: unprefixedPathname
|
|
747
|
-
});
|
|
748
|
-
const placeholderRemovedPathname = standardizedPathname.replace(
|
|
749
|
-
`/${prefixPlaceHolder}`,
|
|
750
|
-
""
|
|
751
|
-
);
|
|
752
|
-
const defaultNamespaces = routeNamespaces.default ?? [];
|
|
753
|
-
const exactMatchNamespaces = routeNamespaces[standardizedPathname] ?? routeNamespaces[placeholderRemovedPathname];
|
|
754
|
-
if (exactMatchNamespaces) {
|
|
755
|
-
return [...defaultNamespaces, ...exactMatchNamespaces];
|
|
756
|
-
}
|
|
757
|
-
let bestMatch = "";
|
|
758
|
-
let bestNamespaces;
|
|
759
|
-
const prefixPatterns = Object.keys(routeNamespaces).filter(
|
|
760
|
-
(pattern) => pattern.endsWith("/*")
|
|
761
|
-
);
|
|
762
|
-
for (const pattern of prefixPatterns) {
|
|
763
|
-
const basePath = pattern.replace(/\/\*$/, "");
|
|
764
|
-
if (standardizedPathname.startsWith(basePath)) {
|
|
765
|
-
if (basePath.length > bestMatch.length) {
|
|
766
|
-
bestMatch = basePath;
|
|
767
|
-
bestNamespaces = routeNamespaces[pattern];
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
const matchedNamespaces = bestNamespaces ?? routeNamespaces["/*"] ?? fallbackNamespaces ?? [];
|
|
772
|
-
if (matchedNamespaces.length > 0) {
|
|
773
|
-
return [...defaultNamespaces, ...matchedNamespaces];
|
|
774
|
-
} else {
|
|
775
|
-
return [...defaultNamespaces];
|
|
776
|
-
}
|
|
777
|
-
};
|
|
778
|
-
|
|
779
|
-
// src/modules/intor-messages-loader/intor-messages-loader.ts
|
|
780
|
-
var intorMessagesLoader = async ({
|
|
817
|
+
// src/modules/messages/get-messages.ts
|
|
818
|
+
var getMessages = async ({
|
|
781
819
|
config,
|
|
782
820
|
locale,
|
|
783
821
|
pathname
|
|
784
822
|
}) => {
|
|
785
|
-
const
|
|
786
|
-
const
|
|
823
|
+
const baseLogger = getLogger({ id: config.id });
|
|
824
|
+
const logger = baseLogger.child({ scope: "messages-loader" });
|
|
825
|
+
const loaderOptions = config.loader;
|
|
787
826
|
const fallbackLocales = config.fallbackLocales[locale] || [];
|
|
788
827
|
const namespaces = resolveNamespaces({ config, pathname });
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
namespaces: namespaces.length > 0 ? { count: namespaces.length, list: [...namespaces] } : "All"
|
|
792
|
-
});
|
|
793
|
-
logger.debug("Loader type selected:", {
|
|
794
|
-
loaderType: loaderOptions.type
|
|
828
|
+
logger.debug("Namespaces ready for loading.", {
|
|
829
|
+
namespaces: namespaces.length > 0 ? { count: namespaces.length, list: [...namespaces] } : "All Namespaces"
|
|
795
830
|
});
|
|
831
|
+
logger.debug("Loader type selected.", { loaderType: loaderOptions.type });
|
|
832
|
+
let loadedMessages;
|
|
796
833
|
if (loaderOptions.type === "import") {
|
|
797
|
-
const loadLocalMessages2 =
|
|
798
|
-
|
|
834
|
+
const loadLocalMessages2 = createLoadLocalMessages(loaderOptions.basePath);
|
|
835
|
+
loadedMessages = await loadLocalMessages2({
|
|
799
836
|
...loaderOptions,
|
|
800
837
|
locale,
|
|
801
838
|
fallbackLocales,
|
|
802
839
|
namespaces,
|
|
803
|
-
|
|
840
|
+
cache: config.cache,
|
|
841
|
+
logger: { id: config.id }
|
|
804
842
|
});
|
|
805
843
|
} else if (loaderOptions.type === "api") {
|
|
806
|
-
|
|
844
|
+
loadedMessages = await loadApiMessages({
|
|
807
845
|
...loaderOptions,
|
|
808
846
|
locale,
|
|
809
847
|
fallbackLocales,
|
|
810
848
|
namespaces,
|
|
811
|
-
|
|
812
|
-
});
|
|
813
|
-
} else {
|
|
814
|
-
logger.error("Unknown loader type.", {
|
|
815
|
-
type: loaderOptions.type
|
|
816
|
-
});
|
|
817
|
-
throw new IntorError({
|
|
818
|
-
id: config.id,
|
|
819
|
-
code: "INTOR_UNKNOWN_LOADER_TYPE" /* UNKNOWN_LOADER_TYPE */,
|
|
820
|
-
message: `Unknown loader type: ${loaderOptions.type}`
|
|
849
|
+
logger: { id: config.id }
|
|
821
850
|
});
|
|
822
851
|
}
|
|
823
|
-
if (!
|
|
852
|
+
if (!loadedMessages || Object.keys(loadedMessages).length === 0) {
|
|
824
853
|
logger.warn("No messages found.", { locale, namespaces });
|
|
825
854
|
}
|
|
826
|
-
return
|
|
827
|
-
};
|
|
828
|
-
|
|
829
|
-
// src/modules/intor-runtime/utils/should-load-dynamic-messages.ts
|
|
830
|
-
var shouldLoadDynamicMessages = (loaderOptions, adapter) => {
|
|
831
|
-
if (!loaderOptions) {
|
|
832
|
-
return false;
|
|
833
|
-
}
|
|
834
|
-
const { type, lazyLoad } = loaderOptions;
|
|
835
|
-
if (type === "import") {
|
|
836
|
-
return true;
|
|
837
|
-
}
|
|
838
|
-
if (type === "api") {
|
|
839
|
-
if (adapter === "next-client" && lazyLoad) {
|
|
840
|
-
return false;
|
|
841
|
-
}
|
|
842
|
-
return true;
|
|
843
|
-
}
|
|
844
|
-
return false;
|
|
845
|
-
};
|
|
846
|
-
|
|
847
|
-
// src/shared/utils/merge-static-and-dynamic-messages.ts
|
|
848
|
-
var mergeStaticAndDynamicMessages = (staticMessages = {}, dynamicMessages = {}) => {
|
|
849
|
-
const result = Object.keys(staticMessages).length ? { ...staticMessages } : {};
|
|
850
|
-
for (const locale in dynamicMessages) {
|
|
851
|
-
const dynamic = dynamicMessages[locale];
|
|
852
|
-
if (!result[locale]) {
|
|
853
|
-
result[locale] = dynamic;
|
|
854
|
-
continue;
|
|
855
|
-
}
|
|
856
|
-
result[locale] = {
|
|
857
|
-
...result[locale],
|
|
858
|
-
...dynamic
|
|
859
|
-
};
|
|
860
|
-
}
|
|
861
|
-
return result;
|
|
855
|
+
return loadedMessages;
|
|
862
856
|
};
|
|
863
857
|
|
|
864
|
-
// src/modules/intor
|
|
865
|
-
var
|
|
866
|
-
|
|
867
|
-
|
|
858
|
+
// src/modules/intor/intor.ts
|
|
859
|
+
var intor = async ({
|
|
860
|
+
config,
|
|
861
|
+
adapter,
|
|
862
|
+
adapterRuntime
|
|
868
863
|
}) => {
|
|
869
|
-
const
|
|
870
|
-
const {
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
message: `Unsupported adapter: ${adapter}`
|
|
882
|
-
});
|
|
883
|
-
}
|
|
884
|
-
const adapterRuntime = await resolceAdapterRuntimeLoader({ config });
|
|
885
|
-
const { locale, pathname } = await adapterRuntime({ config, request });
|
|
886
|
-
logger.info("Initialized locale and pathname:", { locale, pathname });
|
|
887
|
-
let dynamicMessages;
|
|
888
|
-
if (shouldLoadDynamicMessages(loaderOptions, adapter)) {
|
|
889
|
-
dynamicMessages = await intorMessagesLoader({ config, locale, pathname });
|
|
864
|
+
const baseLogger = getLogger({ id: config.id, ...config.logger });
|
|
865
|
+
const logger = baseLogger.child({ scope: "intor" });
|
|
866
|
+
logger.info("Start Intor initialization.");
|
|
867
|
+
const { messages, loader } = config;
|
|
868
|
+
let runtime;
|
|
869
|
+
if (adapter) {
|
|
870
|
+
runtime = await adapter(config);
|
|
871
|
+
} else {
|
|
872
|
+
runtime = {
|
|
873
|
+
locale: adapterRuntime?.locale || config.defaultLocale,
|
|
874
|
+
pathname: adapterRuntime?.pathname || ""
|
|
875
|
+
};
|
|
890
876
|
}
|
|
891
|
-
const
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
)
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
877
|
+
const { locale, pathname } = runtime;
|
|
878
|
+
logger.debug("Runtime resolved via adapter/fallback", runtime);
|
|
879
|
+
let loadedMessages;
|
|
880
|
+
if (shouldLoadMessages(loader)) {
|
|
881
|
+
loadedMessages = await getMessages({ config, locale, pathname });
|
|
882
|
+
}
|
|
883
|
+
const mergedMessages = mergeMessages(messages, loadedMessages);
|
|
884
|
+
logger.info("Messages initialized.", {
|
|
885
|
+
staticMessages: { enabled: !!messages },
|
|
886
|
+
loadedMessages: {
|
|
887
|
+
enabled: !!loadedMessages,
|
|
888
|
+
...loader ? { loaderType: loader.type, lazyLoad: !!loader.lazyLoad } : null
|
|
898
889
|
},
|
|
899
|
-
|
|
900
|
-
fromLoader: !!dynamicMessages,
|
|
901
|
-
...loaderOptions ? {
|
|
902
|
-
loaderType: loaderOptions.type,
|
|
903
|
-
lazyLoad: !!loaderOptions.lazyLoad
|
|
904
|
-
} : null
|
|
905
|
-
},
|
|
906
|
-
mergedMessages: messages
|
|
890
|
+
mergedMessages
|
|
907
891
|
});
|
|
908
892
|
return {
|
|
909
893
|
config,
|
|
910
894
|
initialLocale: locale,
|
|
911
895
|
pathname,
|
|
912
|
-
messages
|
|
913
|
-
};
|
|
914
|
-
};
|
|
915
|
-
|
|
916
|
-
// src/modules/intor/intor.ts
|
|
917
|
-
var intor = async ({
|
|
918
|
-
request,
|
|
919
|
-
config
|
|
920
|
-
}) => {
|
|
921
|
-
const logger = logry({
|
|
922
|
-
id: config.id,
|
|
923
|
-
scope: "intor",
|
|
924
|
-
...config.logger
|
|
925
|
-
});
|
|
926
|
-
logger.info("Starting Intor initialization:", {
|
|
927
|
-
adapter: config.adapter
|
|
928
|
-
});
|
|
929
|
-
const runtime = await intorRuntime({ config, request });
|
|
930
|
-
switch (config.adapter) {
|
|
931
|
-
case "next-server": {
|
|
932
|
-
return runtime;
|
|
933
|
-
}
|
|
934
|
-
case "next-client": {
|
|
935
|
-
return runtime;
|
|
936
|
-
}
|
|
937
|
-
default: {
|
|
938
|
-
logger.error("Unsupported adapter:", { adapter: config.adapter });
|
|
939
|
-
throw new IntorError({
|
|
940
|
-
id: config.id,
|
|
941
|
-
code: "INTOR_UNSUPPORTED_ADAPTER" /* UNSUPPORTED_ADAPTER */,
|
|
942
|
-
message: `Unsupported adapter: ${config.adapter}`
|
|
943
|
-
});
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
};
|
|
947
|
-
var createIntor = (config, translateHandlers) => {
|
|
948
|
-
return async (request) => {
|
|
949
|
-
const { initialLocale, messages } = await intor({ config, request });
|
|
950
|
-
const translator = new Translator({
|
|
951
|
-
locale: initialLocale,
|
|
952
|
-
messages,
|
|
953
|
-
fallbackLocales: config.fallbackLocales,
|
|
954
|
-
loadingMessage: config.translator?.loadingMessage,
|
|
955
|
-
placeholder: config.translator?.placeholder,
|
|
956
|
-
handlers: translateHandlers
|
|
957
|
-
});
|
|
958
|
-
return translator;
|
|
896
|
+
messages: mergedMessages
|
|
959
897
|
};
|
|
960
898
|
};
|
|
961
899
|
|
|
962
|
-
export {
|
|
900
|
+
export { PREFIX_PLACEHOLDER, clearLoggerPool, clearMessagesPool, extractPathname, getMessages, intor, loadApiMessages, loadLocalMessages, mergeMessages, normalizeCacheKey, normalizeLocale, normalizePathname, resolveNamespaces, resolvePreferredLocale, standardizePathname };
|