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