translate-kit 0.2.0 → 0.3.1
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/cli.js +532 -114
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +17 -17
package/dist/cli.js
CHANGED
|
@@ -45,7 +45,7 @@ var init_config = __esm({
|
|
|
45
45
|
translation: z.object({
|
|
46
46
|
batchSize: z.number().int().positive().default(50),
|
|
47
47
|
context: z.string().optional(),
|
|
48
|
-
glossary: z.record(z.string()).optional(),
|
|
48
|
+
glossary: z.record(z.string(), z.string()).optional(),
|
|
49
49
|
tone: z.string().optional(),
|
|
50
50
|
retries: z.number().int().min(0).default(2),
|
|
51
51
|
concurrency: z.number().int().positive().default(3),
|
|
@@ -914,7 +914,7 @@ async function generateKeysBatchWithRetry(model, strings, retries) {
|
|
|
914
914
|
lastError = error;
|
|
915
915
|
if (attempt < retries) {
|
|
916
916
|
const delay = Math.min(Math.pow(2, attempt) * 1e3, 3e4);
|
|
917
|
-
await new Promise((
|
|
917
|
+
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
918
918
|
}
|
|
919
919
|
}
|
|
920
920
|
}
|
|
@@ -1028,6 +1028,18 @@ function hasUseTranslationsImport(ast, importSource) {
|
|
|
1028
1028
|
}
|
|
1029
1029
|
return false;
|
|
1030
1030
|
}
|
|
1031
|
+
function hasGetTranslationsImport(ast, importSource) {
|
|
1032
|
+
for (const node of ast.program.body) {
|
|
1033
|
+
if (node.type === "ImportDeclaration" && node.source.value === importSource) {
|
|
1034
|
+
for (const spec of node.specifiers) {
|
|
1035
|
+
if (spec.type === "ImportSpecifier" && spec.imported.type === "Identifier" && spec.imported.name === "getTranslations") {
|
|
1036
|
+
return true;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
return false;
|
|
1042
|
+
}
|
|
1031
1043
|
function transformConditionalBranch(node, textToKey) {
|
|
1032
1044
|
if (node.type === "ConditionalExpression") {
|
|
1033
1045
|
const cons = transformConditionalBranch(
|
|
@@ -1131,6 +1143,8 @@ function transform(ast, textToKey, options = {}) {
|
|
|
1131
1143
|
return transformInline(ast, textToKey, options);
|
|
1132
1144
|
}
|
|
1133
1145
|
const importSource = options.i18nImport ?? "next-intl";
|
|
1146
|
+
const supportsServerSplit = importSource === "next-intl";
|
|
1147
|
+
const isClient = !supportsServerSplit || options.forceClient || detectClientFile(ast);
|
|
1134
1148
|
let stringsWrapped = 0;
|
|
1135
1149
|
const componentsNeedingT = /* @__PURE__ */ new Set();
|
|
1136
1150
|
traverse2(ast, {
|
|
@@ -1232,7 +1246,12 @@ function transform(ast, textToKey, options = {}) {
|
|
|
1232
1246
|
const key = textToKey[text2];
|
|
1233
1247
|
const args = [t2.stringLiteral(key)];
|
|
1234
1248
|
if (templateInfo && templateInfo.placeholders.length > 0) {
|
|
1235
|
-
args.push(
|
|
1249
|
+
args.push(
|
|
1250
|
+
buildValuesObject(
|
|
1251
|
+
templateInfo.expressions,
|
|
1252
|
+
templateInfo.placeholders
|
|
1253
|
+
)
|
|
1254
|
+
);
|
|
1236
1255
|
}
|
|
1237
1256
|
path.node.value = t2.jsxExpressionContainer(
|
|
1238
1257
|
t2.callExpression(t2.identifier("t"), args)
|
|
@@ -1281,7 +1300,12 @@ function transform(ast, textToKey, options = {}) {
|
|
|
1281
1300
|
const key = textToKey[text2];
|
|
1282
1301
|
const args = [t2.stringLiteral(key)];
|
|
1283
1302
|
if (templateInfo && templateInfo.placeholders.length > 0) {
|
|
1284
|
-
args.push(
|
|
1303
|
+
args.push(
|
|
1304
|
+
buildValuesObject(
|
|
1305
|
+
templateInfo.expressions,
|
|
1306
|
+
templateInfo.placeholders
|
|
1307
|
+
)
|
|
1308
|
+
);
|
|
1285
1309
|
}
|
|
1286
1310
|
path.node.value = t2.callExpression(t2.identifier("t"), args);
|
|
1287
1311
|
stringsWrapped++;
|
|
@@ -1291,43 +1315,86 @@ function transform(ast, textToKey, options = {}) {
|
|
|
1291
1315
|
if (stringsWrapped === 0) {
|
|
1292
1316
|
return { code: generate(ast).code, stringsWrapped: 0, modified: false };
|
|
1293
1317
|
}
|
|
1294
|
-
if (
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
t2.
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1318
|
+
if (isClient) {
|
|
1319
|
+
if (!hasUseTranslationsImport(ast, importSource)) {
|
|
1320
|
+
const importDecl = t2.importDeclaration(
|
|
1321
|
+
[
|
|
1322
|
+
t2.importSpecifier(
|
|
1323
|
+
t2.identifier("useTranslations"),
|
|
1324
|
+
t2.identifier("useTranslations")
|
|
1325
|
+
)
|
|
1326
|
+
],
|
|
1327
|
+
t2.stringLiteral(importSource)
|
|
1328
|
+
);
|
|
1329
|
+
const lastImportIndex = findLastImportIndex(ast);
|
|
1330
|
+
if (lastImportIndex >= 0) {
|
|
1331
|
+
ast.program.body.splice(lastImportIndex + 1, 0, importDecl);
|
|
1332
|
+
} else {
|
|
1333
|
+
ast.program.body.unshift(importDecl);
|
|
1334
|
+
}
|
|
1309
1335
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1336
|
+
traverse2(ast, {
|
|
1337
|
+
FunctionDeclaration(path) {
|
|
1338
|
+
const name = path.node.id?.name;
|
|
1339
|
+
if (!name || !componentsNeedingT.has(name)) return;
|
|
1340
|
+
injectTDeclaration(path);
|
|
1341
|
+
},
|
|
1342
|
+
VariableDeclarator(path) {
|
|
1343
|
+
if (path.node.id.type !== "Identifier") return;
|
|
1344
|
+
const name = path.node.id.name;
|
|
1345
|
+
if (!componentsNeedingT.has(name)) return;
|
|
1346
|
+
const init = path.node.init;
|
|
1347
|
+
if (!init) return;
|
|
1348
|
+
if (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression") {
|
|
1349
|
+
if (init.body.type === "BlockStatement") {
|
|
1350
|
+
injectTIntoBlock(init.body);
|
|
1351
|
+
}
|
|
1326
1352
|
}
|
|
1353
|
+
},
|
|
1354
|
+
noScope: true
|
|
1355
|
+
});
|
|
1356
|
+
} else {
|
|
1357
|
+
const serverSource = `${importSource}/server`;
|
|
1358
|
+
if (!hasGetTranslationsImport(ast, serverSource)) {
|
|
1359
|
+
const importDecl = t2.importDeclaration(
|
|
1360
|
+
[
|
|
1361
|
+
t2.importSpecifier(
|
|
1362
|
+
t2.identifier("getTranslations"),
|
|
1363
|
+
t2.identifier("getTranslations")
|
|
1364
|
+
)
|
|
1365
|
+
],
|
|
1366
|
+
t2.stringLiteral(serverSource)
|
|
1367
|
+
);
|
|
1368
|
+
const lastImportIndex = findLastImportIndex(ast);
|
|
1369
|
+
if (lastImportIndex >= 0) {
|
|
1370
|
+
ast.program.body.splice(lastImportIndex + 1, 0, importDecl);
|
|
1371
|
+
} else {
|
|
1372
|
+
ast.program.body.unshift(importDecl);
|
|
1327
1373
|
}
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
|
|
1374
|
+
}
|
|
1375
|
+
traverse2(ast, {
|
|
1376
|
+
FunctionDeclaration(path) {
|
|
1377
|
+
const name = path.node.id?.name;
|
|
1378
|
+
if (!name || !componentsNeedingT.has(name)) return;
|
|
1379
|
+
path.node.async = true;
|
|
1380
|
+
injectAsyncTIntoBlock(path.node.body);
|
|
1381
|
+
},
|
|
1382
|
+
VariableDeclarator(path) {
|
|
1383
|
+
if (path.node.id.type !== "Identifier") return;
|
|
1384
|
+
const name = path.node.id.name;
|
|
1385
|
+
if (!componentsNeedingT.has(name)) return;
|
|
1386
|
+
const init = path.node.init;
|
|
1387
|
+
if (!init) return;
|
|
1388
|
+
if (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression") {
|
|
1389
|
+
init.async = true;
|
|
1390
|
+
if (init.body.type === "BlockStatement") {
|
|
1391
|
+
injectAsyncTIntoBlock(init.body);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
},
|
|
1395
|
+
noScope: true
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1331
1398
|
const output = generate(ast, { retainLines: false });
|
|
1332
1399
|
return { code: output.code, stringsWrapped, modified: true };
|
|
1333
1400
|
}
|
|
@@ -1339,7 +1406,7 @@ function injectTDeclaration(path) {
|
|
|
1339
1406
|
function injectTIntoBlock(block) {
|
|
1340
1407
|
for (const stmt of block.body) {
|
|
1341
1408
|
if (stmt.type === "VariableDeclaration" && stmt.declarations.some(
|
|
1342
|
-
(d) => d.id.type === "Identifier" && d.id.name === "t" && d.init?.type === "CallExpression" && d.init.callee.type === "Identifier" && d.init.callee.name === "useTranslations"
|
|
1409
|
+
(d) => d.id.type === "Identifier" && d.id.name === "t" && (d.init?.type === "CallExpression" && d.init.callee.type === "Identifier" && (d.init.callee.name === "useTranslations" || d.init.callee.name === "getTranslations") || d.init?.type === "AwaitExpression" && d.init.argument.type === "CallExpression" && d.init.argument.callee.type === "Identifier" && d.init.argument.callee.name === "getTranslations")
|
|
1343
1410
|
)) {
|
|
1344
1411
|
return;
|
|
1345
1412
|
}
|
|
@@ -1352,7 +1419,25 @@ function injectTIntoBlock(block) {
|
|
|
1352
1419
|
]);
|
|
1353
1420
|
block.body.unshift(tDecl);
|
|
1354
1421
|
}
|
|
1355
|
-
function
|
|
1422
|
+
function injectAsyncTIntoBlock(block) {
|
|
1423
|
+
for (const stmt of block.body) {
|
|
1424
|
+
if (stmt.type === "VariableDeclaration" && stmt.declarations.some(
|
|
1425
|
+
(d) => d.id.type === "Identifier" && d.id.name === "t" && (d.init?.type === "AwaitExpression" && d.init.argument.type === "CallExpression" && d.init.argument.callee.type === "Identifier" && d.init.argument.callee.name === "getTranslations" || d.init?.type === "CallExpression" && d.init.callee.type === "Identifier" && d.init.callee.name === "getTranslations")
|
|
1426
|
+
)) {
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
const tDecl = t2.variableDeclaration("const", [
|
|
1431
|
+
t2.variableDeclarator(
|
|
1432
|
+
t2.identifier("t"),
|
|
1433
|
+
t2.awaitExpression(
|
|
1434
|
+
t2.callExpression(t2.identifier("getTranslations"), [])
|
|
1435
|
+
)
|
|
1436
|
+
)
|
|
1437
|
+
]);
|
|
1438
|
+
block.body.unshift(tDecl);
|
|
1439
|
+
}
|
|
1440
|
+
function detectClientFile(ast) {
|
|
1356
1441
|
if (ast.program.directives) {
|
|
1357
1442
|
for (const directive of ast.program.directives) {
|
|
1358
1443
|
if (directive.value?.value === "use client") {
|
|
@@ -1365,7 +1450,18 @@ function isClientFile(ast) {
|
|
|
1365
1450
|
return true;
|
|
1366
1451
|
}
|
|
1367
1452
|
}
|
|
1368
|
-
|
|
1453
|
+
let usesHooks = false;
|
|
1454
|
+
traverse2(ast, {
|
|
1455
|
+
CallExpression(path) {
|
|
1456
|
+
if (usesHooks) return;
|
|
1457
|
+
const callee = path.node.callee;
|
|
1458
|
+
if (callee.type === "Identifier" && /^use[A-Z]/.test(callee.name)) {
|
|
1459
|
+
usesHooks = true;
|
|
1460
|
+
}
|
|
1461
|
+
},
|
|
1462
|
+
noScope: true
|
|
1463
|
+
});
|
|
1464
|
+
return usesHooks;
|
|
1369
1465
|
}
|
|
1370
1466
|
function hasInlineImport(ast, componentPath) {
|
|
1371
1467
|
let hasT = false;
|
|
@@ -1385,13 +1481,46 @@ function hasInlineImport(ast, componentPath) {
|
|
|
1385
1481
|
}
|
|
1386
1482
|
return { hasT, hasHook };
|
|
1387
1483
|
}
|
|
1484
|
+
function normalizeInlineImports(ast, componentPath, isClient) {
|
|
1485
|
+
const validSources = /* @__PURE__ */ new Set([
|
|
1486
|
+
componentPath,
|
|
1487
|
+
`${componentPath}-server`,
|
|
1488
|
+
`${componentPath}/t-server`
|
|
1489
|
+
]);
|
|
1490
|
+
const desiredSource = isClient ? componentPath : `${componentPath}-server`;
|
|
1491
|
+
let changed = false;
|
|
1492
|
+
for (const node of ast.program.body) {
|
|
1493
|
+
if (node.type !== "ImportDeclaration") continue;
|
|
1494
|
+
if (!validSources.has(node.source.value)) continue;
|
|
1495
|
+
if (node.source.value !== desiredSource) {
|
|
1496
|
+
node.source.value = desiredSource;
|
|
1497
|
+
changed = true;
|
|
1498
|
+
}
|
|
1499
|
+
for (const spec of node.specifiers) {
|
|
1500
|
+
if (spec.type !== "ImportSpecifier" || spec.imported.type !== "Identifier") {
|
|
1501
|
+
continue;
|
|
1502
|
+
}
|
|
1503
|
+
if (isClient && spec.imported.name === "createT") {
|
|
1504
|
+
spec.imported = t2.identifier("useT");
|
|
1505
|
+
changed = true;
|
|
1506
|
+
}
|
|
1507
|
+
if (!isClient && spec.imported.name === "useT") {
|
|
1508
|
+
spec.imported = t2.identifier("createT");
|
|
1509
|
+
changed = true;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
return changed;
|
|
1514
|
+
}
|
|
1388
1515
|
function transformInline(ast, textToKey, options) {
|
|
1389
1516
|
const componentPath = options.componentPath ?? "@/components/t";
|
|
1390
|
-
const isClient =
|
|
1517
|
+
const isClient = options.forceClient || detectClientFile(ast);
|
|
1391
1518
|
let stringsWrapped = 0;
|
|
1392
1519
|
const componentsNeedingT = /* @__PURE__ */ new Set();
|
|
1393
1520
|
let needsTComponent = false;
|
|
1394
1521
|
let repaired = false;
|
|
1522
|
+
let boundaryRepaired = false;
|
|
1523
|
+
boundaryRepaired = normalizeInlineImports(ast, componentPath, isClient);
|
|
1395
1524
|
if (!isClient) {
|
|
1396
1525
|
traverse2(ast, {
|
|
1397
1526
|
CallExpression(path) {
|
|
@@ -1523,7 +1652,10 @@ function transformInline(ast, textToKey, options) {
|
|
|
1523
1652
|
];
|
|
1524
1653
|
if (templateInfo && templateInfo.placeholders.length > 0) {
|
|
1525
1654
|
args.push(
|
|
1526
|
-
buildValuesObject(
|
|
1655
|
+
buildValuesObject(
|
|
1656
|
+
templateInfo.expressions,
|
|
1657
|
+
templateInfo.placeholders
|
|
1658
|
+
)
|
|
1527
1659
|
);
|
|
1528
1660
|
}
|
|
1529
1661
|
path.node.value = t2.jsxExpressionContainer(
|
|
@@ -1577,7 +1709,10 @@ function transformInline(ast, textToKey, options) {
|
|
|
1577
1709
|
];
|
|
1578
1710
|
if (templateInfo && templateInfo.placeholders.length > 0) {
|
|
1579
1711
|
args.push(
|
|
1580
|
-
buildValuesObject(
|
|
1712
|
+
buildValuesObject(
|
|
1713
|
+
templateInfo.expressions,
|
|
1714
|
+
templateInfo.placeholders
|
|
1715
|
+
)
|
|
1581
1716
|
);
|
|
1582
1717
|
}
|
|
1583
1718
|
path.node.value = t2.callExpression(t2.identifier("t"), args);
|
|
@@ -1585,10 +1720,10 @@ function transformInline(ast, textToKey, options) {
|
|
|
1585
1720
|
componentsNeedingT.add(compName);
|
|
1586
1721
|
}
|
|
1587
1722
|
});
|
|
1588
|
-
if (stringsWrapped === 0 && !repaired) {
|
|
1723
|
+
if (stringsWrapped === 0 && !repaired && !boundaryRepaired) {
|
|
1589
1724
|
return { code: generate(ast).code, stringsWrapped: 0, modified: false };
|
|
1590
1725
|
}
|
|
1591
|
-
if (stringsWrapped === 0 && repaired) {
|
|
1726
|
+
if (stringsWrapped === 0 && (repaired || boundaryRepaired)) {
|
|
1592
1727
|
const output2 = generate(ast, { retainLines: false });
|
|
1593
1728
|
return { code: output2.code, stringsWrapped: 0, modified: true };
|
|
1594
1729
|
}
|
|
@@ -1687,9 +1822,109 @@ var init_transform = __esm({
|
|
|
1687
1822
|
});
|
|
1688
1823
|
|
|
1689
1824
|
// src/codegen/index.ts
|
|
1825
|
+
import { dirname, extname, join as join2, resolve } from "path";
|
|
1690
1826
|
import { readFile as readFile3, writeFile } from "fs/promises";
|
|
1691
1827
|
import { glob as glob2 } from "tinyglobby";
|
|
1692
1828
|
import pLimit3 from "p-limit";
|
|
1829
|
+
function collectRuntimeImportSources(ast) {
|
|
1830
|
+
const sources = [];
|
|
1831
|
+
for (const node of ast.program.body) {
|
|
1832
|
+
if (node.type === "ImportDeclaration") {
|
|
1833
|
+
if (node.importKind === "type") continue;
|
|
1834
|
+
const allTypeSpecifiers = node.specifiers.length > 0 && node.specifiers.every(
|
|
1835
|
+
(spec) => spec.type === "ImportSpecifier" && spec.importKind === "type"
|
|
1836
|
+
);
|
|
1837
|
+
if (allTypeSpecifiers) continue;
|
|
1838
|
+
sources.push(node.source.value);
|
|
1839
|
+
continue;
|
|
1840
|
+
}
|
|
1841
|
+
if (node.type === "ExportNamedDeclaration" && node.source) {
|
|
1842
|
+
if (node.exportKind !== "type") {
|
|
1843
|
+
sources.push(node.source.value);
|
|
1844
|
+
}
|
|
1845
|
+
continue;
|
|
1846
|
+
}
|
|
1847
|
+
if (node.type === "ExportAllDeclaration") {
|
|
1848
|
+
if (node.exportKind !== "type") {
|
|
1849
|
+
sources.push(node.source.value);
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
return sources;
|
|
1854
|
+
}
|
|
1855
|
+
function resolveFileCandidate(basePath, knownFiles) {
|
|
1856
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
1857
|
+
const resolvedBase = resolve(basePath);
|
|
1858
|
+
const baseExt = extname(resolvedBase);
|
|
1859
|
+
candidates.add(resolvedBase);
|
|
1860
|
+
if (!baseExt) {
|
|
1861
|
+
for (const ext of SOURCE_EXTENSIONS) {
|
|
1862
|
+
candidates.add(resolve(`${resolvedBase}${ext}`));
|
|
1863
|
+
candidates.add(resolve(join2(resolvedBase, `index${ext}`)));
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
if ([".js", ".jsx", ".mjs", ".cjs"].includes(baseExt)) {
|
|
1867
|
+
const noExt = resolvedBase.slice(0, -baseExt.length);
|
|
1868
|
+
for (const ext of SOURCE_EXTENSIONS) {
|
|
1869
|
+
candidates.add(resolve(`${noExt}${ext}`));
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
for (const candidate of candidates) {
|
|
1873
|
+
if (knownFiles.has(candidate)) return candidate;
|
|
1874
|
+
}
|
|
1875
|
+
return null;
|
|
1876
|
+
}
|
|
1877
|
+
function resolveLocalImport(importerPath, source, cwd, knownFiles) {
|
|
1878
|
+
const baseCandidates = [];
|
|
1879
|
+
if (source.startsWith(".")) {
|
|
1880
|
+
baseCandidates.push(resolve(dirname(importerPath), source));
|
|
1881
|
+
} else if (source.startsWith("@/")) {
|
|
1882
|
+
baseCandidates.push(resolve(join2(cwd, "src", source.slice(2))));
|
|
1883
|
+
baseCandidates.push(resolve(join2(cwd, source.slice(2))));
|
|
1884
|
+
} else if (source.startsWith("~/")) {
|
|
1885
|
+
baseCandidates.push(resolve(join2(cwd, source.slice(2))));
|
|
1886
|
+
} else if (source.startsWith("/")) {
|
|
1887
|
+
baseCandidates.push(resolve(join2(cwd, source.slice(1))));
|
|
1888
|
+
} else {
|
|
1889
|
+
return null;
|
|
1890
|
+
}
|
|
1891
|
+
for (const base of baseCandidates) {
|
|
1892
|
+
const resolved = resolveFileCandidate(base, knownFiles);
|
|
1893
|
+
if (resolved) return resolved;
|
|
1894
|
+
}
|
|
1895
|
+
return null;
|
|
1896
|
+
}
|
|
1897
|
+
function buildClientGraph(entries, cwd) {
|
|
1898
|
+
const parsedEntries = entries.filter((e) => e.ast != null);
|
|
1899
|
+
const knownFiles = new Set(parsedEntries.map((e) => e.filePath));
|
|
1900
|
+
const depsByImporter = /* @__PURE__ */ new Map();
|
|
1901
|
+
for (const entry of parsedEntries) {
|
|
1902
|
+
const deps = [];
|
|
1903
|
+
const imports = collectRuntimeImportSources(entry.ast);
|
|
1904
|
+
for (const source of imports) {
|
|
1905
|
+
const dep = resolveLocalImport(entry.filePath, source, cwd, knownFiles);
|
|
1906
|
+
if (dep) deps.push(dep);
|
|
1907
|
+
}
|
|
1908
|
+
depsByImporter.set(entry.filePath, deps);
|
|
1909
|
+
}
|
|
1910
|
+
const clientReachable = /* @__PURE__ */ new Set();
|
|
1911
|
+
const queue = [];
|
|
1912
|
+
for (const entry of parsedEntries) {
|
|
1913
|
+
if (!entry.isClientRoot) continue;
|
|
1914
|
+
clientReachable.add(entry.filePath);
|
|
1915
|
+
queue.push(entry.filePath);
|
|
1916
|
+
}
|
|
1917
|
+
while (queue.length > 0) {
|
|
1918
|
+
const filePath = queue.shift();
|
|
1919
|
+
const deps = depsByImporter.get(filePath) ?? [];
|
|
1920
|
+
for (const dep of deps) {
|
|
1921
|
+
if (clientReachable.has(dep)) continue;
|
|
1922
|
+
clientReachable.add(dep);
|
|
1923
|
+
queue.push(dep);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
return clientReachable;
|
|
1927
|
+
}
|
|
1693
1928
|
async function codegen(options, cwd = process.cwd()) {
|
|
1694
1929
|
const files = await glob2(options.include, {
|
|
1695
1930
|
ignore: options.exclude ?? [],
|
|
@@ -1701,39 +1936,72 @@ async function codegen(options, cwd = process.cwd()) {
|
|
|
1701
1936
|
mode: options.mode,
|
|
1702
1937
|
componentPath: options.componentPath
|
|
1703
1938
|
};
|
|
1704
|
-
const
|
|
1705
|
-
|
|
1706
|
-
const fileResults = await Promise.all(
|
|
1939
|
+
const parseLimit = pLimit3(10);
|
|
1940
|
+
const parsedEntries = await Promise.all(
|
|
1707
1941
|
files.map(
|
|
1708
|
-
(filePath) =>
|
|
1942
|
+
(filePath) => parseLimit(async () => {
|
|
1709
1943
|
const code = await readFile3(filePath, "utf-8");
|
|
1710
|
-
let ast;
|
|
1711
1944
|
try {
|
|
1712
|
-
ast = parseFile(code, filePath);
|
|
1945
|
+
const ast = parseFile(code, filePath);
|
|
1946
|
+
return {
|
|
1947
|
+
filePath,
|
|
1948
|
+
code,
|
|
1949
|
+
ast,
|
|
1950
|
+
isClientRoot: detectClientFile(ast)
|
|
1951
|
+
};
|
|
1713
1952
|
} catch (err) {
|
|
1953
|
+
return {
|
|
1954
|
+
filePath,
|
|
1955
|
+
code,
|
|
1956
|
+
parseError: err instanceof Error ? err.message : String(err),
|
|
1957
|
+
isClientRoot: false
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
})
|
|
1961
|
+
)
|
|
1962
|
+
);
|
|
1963
|
+
const forceClientSet = buildClientGraph(parsedEntries, cwd);
|
|
1964
|
+
const limit = pLimit3(10);
|
|
1965
|
+
let completed = 0;
|
|
1966
|
+
const fileResults = await Promise.all(
|
|
1967
|
+
parsedEntries.map(
|
|
1968
|
+
(entry) => limit(async () => {
|
|
1969
|
+
if (!entry.ast) {
|
|
1714
1970
|
logWarning(
|
|
1715
|
-
`Skipping unparseable file ${filePath}: ${
|
|
1971
|
+
`Skipping unparseable file ${entry.filePath}: ${entry.parseError ?? "unknown parse error"}`
|
|
1716
1972
|
);
|
|
1717
1973
|
completed++;
|
|
1718
1974
|
options.onProgress?.(completed, files.length);
|
|
1719
1975
|
return { modified: false, wrapped: 0, skipped: false };
|
|
1720
1976
|
}
|
|
1721
|
-
const
|
|
1977
|
+
const fileTransformOpts = {
|
|
1978
|
+
...transformOpts,
|
|
1979
|
+
forceClient: forceClientSet.has(entry.filePath)
|
|
1980
|
+
};
|
|
1981
|
+
const result = transform(
|
|
1982
|
+
entry.ast,
|
|
1983
|
+
options.textToKey,
|
|
1984
|
+
fileTransformOpts
|
|
1985
|
+
);
|
|
1722
1986
|
if (result.modified) {
|
|
1723
1987
|
try {
|
|
1724
|
-
parseFile(result.code, filePath);
|
|
1988
|
+
parseFile(result.code, entry.filePath);
|
|
1725
1989
|
} catch {
|
|
1726
1990
|
logWarning(
|
|
1727
|
-
`Codegen produced invalid syntax for ${filePath}, file was NOT modified.`
|
|
1991
|
+
`Codegen produced invalid syntax for ${entry.filePath}, file was NOT modified.`
|
|
1728
1992
|
);
|
|
1729
1993
|
completed++;
|
|
1730
1994
|
options.onProgress?.(completed, files.length);
|
|
1731
1995
|
return { modified: false, wrapped: 0, skipped: true };
|
|
1732
1996
|
}
|
|
1733
|
-
await writeFile(filePath, result.code, "utf-8");
|
|
1997
|
+
await writeFile(entry.filePath, result.code, "utf-8");
|
|
1734
1998
|
completed++;
|
|
1735
1999
|
options.onProgress?.(completed, files.length);
|
|
1736
|
-
return {
|
|
2000
|
+
return {
|
|
2001
|
+
modified: true,
|
|
2002
|
+
wrapped: result.stringsWrapped,
|
|
2003
|
+
skipped: false
|
|
2004
|
+
};
|
|
1737
2005
|
}
|
|
1738
2006
|
completed++;
|
|
1739
2007
|
options.onProgress?.(completed, files.length);
|
|
@@ -1758,12 +2026,23 @@ async function codegen(options, cwd = process.cwd()) {
|
|
|
1758
2026
|
filesSkipped
|
|
1759
2027
|
};
|
|
1760
2028
|
}
|
|
2029
|
+
var SOURCE_EXTENSIONS;
|
|
1761
2030
|
var init_codegen = __esm({
|
|
1762
2031
|
"src/codegen/index.ts"() {
|
|
1763
2032
|
"use strict";
|
|
1764
2033
|
init_parser();
|
|
1765
2034
|
init_transform();
|
|
1766
2035
|
init_logger();
|
|
2036
|
+
SOURCE_EXTENSIONS = [
|
|
2037
|
+
".ts",
|
|
2038
|
+
".tsx",
|
|
2039
|
+
".js",
|
|
2040
|
+
".jsx",
|
|
2041
|
+
".mts",
|
|
2042
|
+
".cts",
|
|
2043
|
+
".mjs",
|
|
2044
|
+
".cjs"
|
|
2045
|
+
];
|
|
1767
2046
|
}
|
|
1768
2047
|
});
|
|
1769
2048
|
|
|
@@ -1907,7 +2186,7 @@ async function translateBatchWithRetry(input, retries) {
|
|
|
1907
2186
|
lastError = error;
|
|
1908
2187
|
if (attempt < retries) {
|
|
1909
2188
|
const delay = Math.min(Math.pow(2, attempt) * 1e3, 3e4);
|
|
1910
|
-
await new Promise((
|
|
2189
|
+
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
1911
2190
|
}
|
|
1912
2191
|
}
|
|
1913
2192
|
}
|
|
@@ -1974,9 +2253,9 @@ var init_translate = __esm({
|
|
|
1974
2253
|
|
|
1975
2254
|
// src/writer.ts
|
|
1976
2255
|
import { writeFile as writeFile2, mkdir } from "fs/promises";
|
|
1977
|
-
import { join as
|
|
2256
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
1978
2257
|
async function writeTranslation(filePath, flatEntries, options) {
|
|
1979
|
-
await mkdir(
|
|
2258
|
+
await mkdir(dirname2(filePath), { recursive: true });
|
|
1980
2259
|
const data = options?.flat ? flatEntries : unflatten(flatEntries);
|
|
1981
2260
|
const content = JSON.stringify(data, null, 2) + "\n";
|
|
1982
2261
|
await writeFile2(filePath, content, "utf-8");
|
|
@@ -1993,8 +2272,8 @@ async function writeLockFile(messagesDir, sourceFlat, existingLock, translatedKe
|
|
|
1993
2272
|
delete lock[key];
|
|
1994
2273
|
}
|
|
1995
2274
|
}
|
|
1996
|
-
const lockPath =
|
|
1997
|
-
await mkdir(
|
|
2275
|
+
const lockPath = join3(messagesDir, ".translate-lock.json");
|
|
2276
|
+
await mkdir(dirname2(lockPath), { recursive: true });
|
|
1998
2277
|
const content = JSON.stringify(lock, null, 2) + "\n";
|
|
1999
2278
|
await writeFile2(lockPath, content, "utf-8");
|
|
2000
2279
|
}
|
|
@@ -2007,10 +2286,10 @@ var init_writer = __esm({
|
|
|
2007
2286
|
});
|
|
2008
2287
|
|
|
2009
2288
|
// src/pipeline.ts
|
|
2010
|
-
import { join as
|
|
2289
|
+
import { join as join4 } from "path";
|
|
2011
2290
|
import { readFile as readFile4, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
|
|
2012
2291
|
async function loadMapFile(messagesDir) {
|
|
2013
|
-
const mapPath =
|
|
2292
|
+
const mapPath = join4(messagesDir, ".translate-map.json");
|
|
2014
2293
|
let content;
|
|
2015
2294
|
try {
|
|
2016
2295
|
content = await readFile4(mapPath, "utf-8");
|
|
@@ -2027,7 +2306,7 @@ async function loadMapFile(messagesDir) {
|
|
|
2027
2306
|
}
|
|
2028
2307
|
}
|
|
2029
2308
|
async function writeMapFile(messagesDir, map) {
|
|
2030
|
-
const mapPath =
|
|
2309
|
+
const mapPath = join4(messagesDir, ".translate-map.json");
|
|
2031
2310
|
await mkdir2(messagesDir, { recursive: true });
|
|
2032
2311
|
const content = JSON.stringify(map, null, 2) + "\n";
|
|
2033
2312
|
await writeFile3(mapPath, content, "utf-8");
|
|
@@ -2080,7 +2359,7 @@ async function runScanStep(input) {
|
|
|
2080
2359
|
sourceFlat[key] = text2;
|
|
2081
2360
|
}
|
|
2082
2361
|
if (mode !== "inline") {
|
|
2083
|
-
const sourceFile =
|
|
2362
|
+
const sourceFile = join4(
|
|
2084
2363
|
config.messagesDir,
|
|
2085
2364
|
`${config.sourceLocale}.json`
|
|
2086
2365
|
);
|
|
@@ -2134,7 +2413,7 @@ async function runTranslateStep(input) {
|
|
|
2134
2413
|
sourceFlat[key] = text2;
|
|
2135
2414
|
}
|
|
2136
2415
|
} else {
|
|
2137
|
-
const sourceFile =
|
|
2416
|
+
const sourceFile = join4(
|
|
2138
2417
|
config.messagesDir,
|
|
2139
2418
|
`${config.sourceLocale}.json`
|
|
2140
2419
|
);
|
|
@@ -2145,7 +2424,7 @@ async function runTranslateStep(input) {
|
|
|
2145
2424
|
const localeResults = [];
|
|
2146
2425
|
for (const locale of locales) {
|
|
2147
2426
|
const start = Date.now();
|
|
2148
|
-
const targetFile =
|
|
2427
|
+
const targetFile = join4(config.messagesDir, `${locale}.json`);
|
|
2149
2428
|
const targetRaw = await loadJsonFile(targetFile);
|
|
2150
2429
|
const targetFlat = flatten(targetRaw);
|
|
2151
2430
|
let lockData = await loadLockFile(config.messagesDir);
|
|
@@ -2311,8 +2590,9 @@ var init_cli_utils = __esm({
|
|
|
2311
2590
|
});
|
|
2312
2591
|
|
|
2313
2592
|
// src/templates/t-component.ts
|
|
2314
|
-
function serverTemplate(clientBasename) {
|
|
2315
|
-
|
|
2593
|
+
function serverTemplate(clientBasename, opts) {
|
|
2594
|
+
if (!opts) {
|
|
2595
|
+
return `import type { ReactNode } from "react";
|
|
2316
2596
|
import { cache } from "react";
|
|
2317
2597
|
export { I18nProvider } from "./${clientBasename}";
|
|
2318
2598
|
|
|
@@ -2339,12 +2619,134 @@ export function createT(messages?: Messages) {
|
|
|
2339
2619
|
return raw.replace(/\\{(\\w+)\\}/g, (_, k) => String(values[k] ?? \`{\${k}}\`));
|
|
2340
2620
|
};
|
|
2341
2621
|
}
|
|
2622
|
+
`;
|
|
2623
|
+
}
|
|
2624
|
+
const allLocales = [opts.sourceLocale, ...opts.targetLocales];
|
|
2625
|
+
const allLocalesStr = allLocales.map((l) => `"${l}"`).join(", ");
|
|
2626
|
+
return `import type { ReactNode } from "react";
|
|
2627
|
+
import { cache } from "react";
|
|
2628
|
+
export { I18nProvider } from "./${clientBasename}";
|
|
2629
|
+
|
|
2630
|
+
type Messages = Record<string, string>;
|
|
2631
|
+
|
|
2632
|
+
const supported = [${allLocalesStr}] as const;
|
|
2633
|
+
type Locale = (typeof supported)[number];
|
|
2634
|
+
const defaultLocale: Locale = "${opts.sourceLocale}";
|
|
2635
|
+
const messagesDir = "${opts.messagesDir}";
|
|
2636
|
+
|
|
2637
|
+
function parseAcceptLanguage(header: string): Locale {
|
|
2638
|
+
const langs = header
|
|
2639
|
+
.split(",")
|
|
2640
|
+
.map((part) => {
|
|
2641
|
+
const [lang, q] = part.trim().split(";q=");
|
|
2642
|
+
return { lang: lang.split("-")[0].toLowerCase(), q: q ? parseFloat(q) : 1 };
|
|
2643
|
+
})
|
|
2644
|
+
.sort((a, b) => b.q - a.q);
|
|
2645
|
+
|
|
2646
|
+
for (const { lang } of langs) {
|
|
2647
|
+
if (supported.includes(lang as Locale)) return lang as Locale;
|
|
2648
|
+
}
|
|
2649
|
+
return defaultLocale;
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
const getLocaleStore = cache(() => ({ current: null as string | null }));
|
|
2653
|
+
|
|
2654
|
+
export function setLocale(locale: string) {
|
|
2655
|
+
getLocaleStore().current = locale;
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
// Per-request cached message loading \u2014 works even when layout is cached during client-side navigation
|
|
2659
|
+
// Uses dynamic imports so this file can be safely imported from client components
|
|
2660
|
+
const getCachedMessages = cache(async (): Promise<Messages> => {
|
|
2661
|
+
let locale: Locale | null = getLocaleStore().current as Locale | null;
|
|
2662
|
+
|
|
2663
|
+
if (!locale) {
|
|
2664
|
+
try {
|
|
2665
|
+
const { cookies } = await import("next/headers");
|
|
2666
|
+
const c = await cookies();
|
|
2667
|
+
const cookieLocale = c.get("NEXT_LOCALE")?.value;
|
|
2668
|
+
if (cookieLocale && supported.includes(cookieLocale as Locale)) {
|
|
2669
|
+
locale = cookieLocale as Locale;
|
|
2670
|
+
}
|
|
2671
|
+
} catch {}
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
if (!locale) {
|
|
2675
|
+
const { headers } = await import("next/headers");
|
|
2676
|
+
const h = await headers();
|
|
2677
|
+
const acceptLang = h.get("accept-language") ?? "";
|
|
2678
|
+
locale = parseAcceptLanguage(acceptLang);
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
if (locale === defaultLocale) return {};
|
|
2682
|
+
const { readFile } = await import("node:fs/promises");
|
|
2683
|
+
const { join } = await import("node:path");
|
|
2684
|
+
try {
|
|
2685
|
+
const filePath = join(process.cwd(), messagesDir, \`\${locale}.json\`);
|
|
2686
|
+
const content = await readFile(filePath, "utf-8");
|
|
2687
|
+
return JSON.parse(content);
|
|
2688
|
+
} catch {
|
|
2689
|
+
return {};
|
|
2690
|
+
}
|
|
2691
|
+
});
|
|
2692
|
+
|
|
2693
|
+
// Per-request message store (populated by setServerMessages in layout)
|
|
2694
|
+
const getMessageStore = cache(() => ({ current: null as Messages | null }));
|
|
2695
|
+
|
|
2696
|
+
export function setServerMessages(messages: Messages) {
|
|
2697
|
+
getMessageStore().current = messages;
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
async function resolveMessages(explicit?: Messages): Promise<Messages> {
|
|
2701
|
+
if (explicit) return explicit;
|
|
2702
|
+
const store = getMessageStore().current;
|
|
2703
|
+
if (store) return store;
|
|
2704
|
+
return getCachedMessages();
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
export async function T({ id, children, messages }: { id?: string; children: ReactNode; messages?: Messages }) {
|
|
2708
|
+
if (!id) return <>{children}</>;
|
|
2709
|
+
const msgs = await resolveMessages(messages);
|
|
2710
|
+
// Populate store so sync createT() calls in the same request benefit
|
|
2711
|
+
if (!messages && !getMessageStore().current) {
|
|
2712
|
+
getMessageStore().current = msgs;
|
|
2713
|
+
}
|
|
2714
|
+
return <>{msgs[id] ?? children}</>;
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
type TFn = (text: string, id?: string, values?: Record<string, string | number>) => string;
|
|
2718
|
+
|
|
2719
|
+
// Backward-compatible: works both as sync createT() and async await createT()
|
|
2720
|
+
// - Sync: reads from store (works when layout called setServerMessages)
|
|
2721
|
+
// - Async: lazily loads messages from filesystem (works during client-side navigation)
|
|
2722
|
+
export function createT(messages?: Messages): TFn & PromiseLike<TFn> {
|
|
2723
|
+
const t: TFn = (text, id, values) => {
|
|
2724
|
+
const msgs = messages ?? getMessageStore().current ?? {};
|
|
2725
|
+
const raw = id ? (msgs[id] ?? text) : text;
|
|
2726
|
+
if (!values) return raw;
|
|
2727
|
+
return raw.replace(/\\{(\\w+)\\}/g, (_, k) => String(values[k] ?? \`{\${k}}\`));
|
|
2728
|
+
};
|
|
2729
|
+
|
|
2730
|
+
const asyncResult = resolveMessages(messages).then(msgs => {
|
|
2731
|
+
if (!messages && !getMessageStore().current) {
|
|
2732
|
+
getMessageStore().current = msgs;
|
|
2733
|
+
}
|
|
2734
|
+
const bound: TFn = (text, id, values) => {
|
|
2735
|
+
const raw = id ? (msgs[id] ?? text) : text;
|
|
2736
|
+
if (!values) return raw;
|
|
2737
|
+
return raw.replace(/\\{(\\w+)\\}/g, (_, k) => String(values[k] ?? \`{\${k}}\`));
|
|
2738
|
+
};
|
|
2739
|
+
return bound;
|
|
2740
|
+
});
|
|
2741
|
+
|
|
2742
|
+
return Object.assign(t, { then: asyncResult.then.bind(asyncResult) });
|
|
2743
|
+
}
|
|
2342
2744
|
`;
|
|
2343
2745
|
}
|
|
2344
2746
|
function generateI18nHelper(opts) {
|
|
2345
2747
|
const allLocales = [opts.sourceLocale, ...opts.targetLocales];
|
|
2346
2748
|
const allLocalesStr = allLocales.map((l) => `"${l}"`).join(", ");
|
|
2347
|
-
return `import { headers } from "next/headers";
|
|
2749
|
+
return `import { headers, cookies } from "next/headers";
|
|
2348
2750
|
import { readFile } from "node:fs/promises";
|
|
2349
2751
|
import { join } from "node:path";
|
|
2350
2752
|
|
|
@@ -2368,6 +2770,13 @@ function parseAcceptLanguage(header: string): Locale {
|
|
|
2368
2770
|
}
|
|
2369
2771
|
|
|
2370
2772
|
export async function getLocale(): Promise<Locale> {
|
|
2773
|
+
try {
|
|
2774
|
+
const c = await cookies();
|
|
2775
|
+
const cookieLocale = c.get("NEXT_LOCALE")?.value;
|
|
2776
|
+
if (cookieLocale && supported.includes(cookieLocale as Locale)) {
|
|
2777
|
+
return cookieLocale as Locale;
|
|
2778
|
+
}
|
|
2779
|
+
} catch {}
|
|
2371
2780
|
const h = await headers();
|
|
2372
2781
|
const acceptLang = h.get("accept-language") ?? "";
|
|
2373
2782
|
return parseAcceptLanguage(acceptLang);
|
|
@@ -2390,12 +2799,17 @@ var init_t_component = __esm({
|
|
|
2390
2799
|
"src/templates/t-component.ts"() {
|
|
2391
2800
|
"use strict";
|
|
2392
2801
|
CLIENT_TEMPLATE = `"use client";
|
|
2393
|
-
import { createContext, useContext, type ReactNode } from "react";
|
|
2802
|
+
import { createContext, useContext, useEffect, type ReactNode } from "react";
|
|
2394
2803
|
|
|
2395
2804
|
type Messages = Record<string, string>;
|
|
2396
2805
|
const I18nCtx = createContext<Messages>({});
|
|
2397
2806
|
|
|
2398
|
-
export function I18nProvider({ messages = {}, children }: { messages?: Messages; children: ReactNode }) {
|
|
2807
|
+
export function I18nProvider({ messages = {}, locale, children }: { messages?: Messages; locale?: string; children: ReactNode }) {
|
|
2808
|
+
useEffect(() => {
|
|
2809
|
+
if (locale) {
|
|
2810
|
+
document.cookie = \`NEXT_LOCALE=\${locale}; path=/; max-age=31536000; SameSite=Lax\`;
|
|
2811
|
+
}
|
|
2812
|
+
}, [locale]);
|
|
2399
2813
|
return <I18nCtx.Provider value={messages}>{children}</I18nCtx.Provider>;
|
|
2400
2814
|
}
|
|
2401
2815
|
|
|
@@ -2424,17 +2838,17 @@ __export(init_exports, {
|
|
|
2424
2838
|
});
|
|
2425
2839
|
import * as p from "@clack/prompts";
|
|
2426
2840
|
import { existsSync } from "fs";
|
|
2427
|
-
import { basename, join as
|
|
2841
|
+
import { basename, join as join5, relative } from "path";
|
|
2428
2842
|
import { readFile as readFile5, writeFile as writeFile4, mkdir as mkdir3 } from "fs/promises";
|
|
2429
2843
|
function detectIncludePatterns(cwd) {
|
|
2430
2844
|
const patterns = [];
|
|
2431
|
-
if (existsSync(
|
|
2845
|
+
if (existsSync(join5(cwd, "app")))
|
|
2432
2846
|
patterns.push("app/**/*.tsx", "app/**/*.jsx");
|
|
2433
|
-
if (existsSync(
|
|
2847
|
+
if (existsSync(join5(cwd, "src")))
|
|
2434
2848
|
patterns.push("src/**/*.tsx", "src/**/*.jsx");
|
|
2435
|
-
if (existsSync(
|
|
2849
|
+
if (existsSync(join5(cwd, "pages")))
|
|
2436
2850
|
patterns.push("pages/**/*.tsx", "pages/**/*.jsx");
|
|
2437
|
-
if (existsSync(
|
|
2851
|
+
if (existsSync(join5(cwd, "src", "app"))) {
|
|
2438
2852
|
return patterns.filter((p2) => !p2.startsWith("app/"));
|
|
2439
2853
|
}
|
|
2440
2854
|
return patterns.length > 0 ? patterns : ["**/*.tsx", "**/*.jsx"];
|
|
@@ -2447,10 +2861,10 @@ function findPackageInNodeModules(cwd, pkg) {
|
|
|
2447
2861
|
let dir = cwd;
|
|
2448
2862
|
const parts = pkg.split("/");
|
|
2449
2863
|
while (true) {
|
|
2450
|
-
if (existsSync(
|
|
2864
|
+
if (existsSync(join5(dir, "node_modules", ...parts, "package.json"))) {
|
|
2451
2865
|
return true;
|
|
2452
2866
|
}
|
|
2453
|
-
const parent =
|
|
2867
|
+
const parent = join5(dir, "..");
|
|
2454
2868
|
if (parent === dir) break;
|
|
2455
2869
|
dir = parent;
|
|
2456
2870
|
}
|
|
@@ -2530,22 +2944,22 @@ async function safeWriteModifiedFile(filePath, modified, label) {
|
|
|
2530
2944
|
return true;
|
|
2531
2945
|
}
|
|
2532
2946
|
function detectSrcDir(cwd) {
|
|
2533
|
-
return existsSync(
|
|
2947
|
+
return existsSync(join5(cwd, "src", "app"));
|
|
2534
2948
|
}
|
|
2535
2949
|
function resolveComponentPath(cwd, componentPath) {
|
|
2536
2950
|
if (componentPath.startsWith("@/")) {
|
|
2537
2951
|
const rel = componentPath.slice(2);
|
|
2538
|
-
const useSrc = existsSync(
|
|
2539
|
-
return
|
|
2952
|
+
const useSrc = existsSync(join5(cwd, "src"));
|
|
2953
|
+
return join5(cwd, useSrc ? "src" : "", rel);
|
|
2540
2954
|
}
|
|
2541
2955
|
if (componentPath.startsWith("~/")) {
|
|
2542
|
-
return
|
|
2956
|
+
return join5(cwd, componentPath.slice(2));
|
|
2543
2957
|
}
|
|
2544
|
-
return
|
|
2958
|
+
return join5(cwd, componentPath);
|
|
2545
2959
|
}
|
|
2546
2960
|
function findLayoutFile(base) {
|
|
2547
2961
|
for (const ext of ["tsx", "jsx", "ts", "js"]) {
|
|
2548
|
-
const candidate =
|
|
2962
|
+
const candidate = join5(base, "app", `layout.${ext}`);
|
|
2549
2963
|
if (existsSync(candidate)) return candidate;
|
|
2550
2964
|
}
|
|
2551
2965
|
return void 0;
|
|
@@ -2553,7 +2967,7 @@ function findLayoutFile(base) {
|
|
|
2553
2967
|
async function createEmptyMessageFiles(msgDir, locales) {
|
|
2554
2968
|
await mkdir3(msgDir, { recursive: true });
|
|
2555
2969
|
for (const locale of locales) {
|
|
2556
|
-
const msgFile =
|
|
2970
|
+
const msgFile = join5(msgDir, `${locale}.json`);
|
|
2557
2971
|
if (!existsSync(msgFile)) {
|
|
2558
2972
|
await writeFile4(msgFile, "{}\n", "utf-8");
|
|
2559
2973
|
}
|
|
@@ -2573,14 +2987,14 @@ function ensureAsyncLayout(content) {
|
|
|
2573
2987
|
}
|
|
2574
2988
|
async function setupNextIntl(cwd, sourceLocale, targetLocales, messagesDir) {
|
|
2575
2989
|
const useSrc = detectSrcDir(cwd);
|
|
2576
|
-
const base = useSrc ?
|
|
2990
|
+
const base = useSrc ? join5(cwd, "src") : cwd;
|
|
2577
2991
|
const allLocales = [sourceLocale, ...targetLocales];
|
|
2578
2992
|
const filesCreated = [];
|
|
2579
|
-
const i18nDir =
|
|
2993
|
+
const i18nDir = join5(base, "i18n");
|
|
2580
2994
|
await mkdir3(i18nDir, { recursive: true });
|
|
2581
|
-
const requestFile =
|
|
2995
|
+
const requestFile = join5(i18nDir, "request.ts");
|
|
2582
2996
|
if (!existsSync(requestFile)) {
|
|
2583
|
-
const relMessages = relative(i18nDir,
|
|
2997
|
+
const relMessages = relative(i18nDir, join5(cwd, messagesDir));
|
|
2584
2998
|
const allLocalesStr = allLocales.map((l) => `"${l}"`).join(", ");
|
|
2585
2999
|
await writeFile4(
|
|
2586
3000
|
requestFile,
|
|
@@ -2621,7 +3035,7 @@ export default getRequestConfig(async () => {
|
|
|
2621
3035
|
);
|
|
2622
3036
|
filesCreated.push(relative(cwd, requestFile));
|
|
2623
3037
|
}
|
|
2624
|
-
const nextConfigPath =
|
|
3038
|
+
const nextConfigPath = join5(cwd, "next.config.ts");
|
|
2625
3039
|
if (existsSync(nextConfigPath)) {
|
|
2626
3040
|
const content = await readFile5(nextConfigPath, "utf-8");
|
|
2627
3041
|
if (!content.includes("next-intl")) {
|
|
@@ -2671,31 +3085,31 @@ export default getRequestConfig(async () => {
|
|
|
2671
3085
|
}
|
|
2672
3086
|
}
|
|
2673
3087
|
}
|
|
2674
|
-
await createEmptyMessageFiles(
|
|
3088
|
+
await createEmptyMessageFiles(join5(cwd, messagesDir), allLocales);
|
|
2675
3089
|
if (filesCreated.length > 0) {
|
|
2676
3090
|
p.log.success(`next-intl configured: ${filesCreated.join(", ")}`);
|
|
2677
3091
|
}
|
|
2678
3092
|
}
|
|
2679
|
-
async function dropInlineComponents(cwd, componentPath) {
|
|
3093
|
+
async function dropInlineComponents(cwd, componentPath, localeOpts) {
|
|
2680
3094
|
const fsPath = resolveComponentPath(cwd, componentPath);
|
|
2681
|
-
const dir =
|
|
3095
|
+
const dir = join5(fsPath, "..");
|
|
2682
3096
|
await mkdir3(dir, { recursive: true });
|
|
2683
3097
|
const clientFile = `${fsPath}.tsx`;
|
|
2684
3098
|
const serverFile = `${fsPath}-server.tsx`;
|
|
2685
3099
|
const clientBasename = basename(fsPath);
|
|
2686
3100
|
await writeFile4(clientFile, CLIENT_TEMPLATE, "utf-8");
|
|
2687
|
-
await writeFile4(serverFile, serverTemplate(clientBasename), "utf-8");
|
|
3101
|
+
await writeFile4(serverFile, serverTemplate(clientBasename, localeOpts), "utf-8");
|
|
2688
3102
|
const relClient = relative(cwd, clientFile);
|
|
2689
3103
|
const relServer = relative(cwd, serverFile);
|
|
2690
3104
|
p.log.success(`Created inline components: ${relClient}, ${relServer}`);
|
|
2691
3105
|
}
|
|
2692
3106
|
async function setupInlineI18n(cwd, componentPath, sourceLocale, targetLocales, messagesDir) {
|
|
2693
|
-
const useSrc = existsSync(
|
|
2694
|
-
const base = useSrc ?
|
|
3107
|
+
const useSrc = existsSync(join5(cwd, "src"));
|
|
3108
|
+
const base = useSrc ? join5(cwd, "src") : cwd;
|
|
2695
3109
|
const filesCreated = [];
|
|
2696
|
-
const i18nDir =
|
|
3110
|
+
const i18nDir = join5(base, "i18n");
|
|
2697
3111
|
await mkdir3(i18nDir, { recursive: true });
|
|
2698
|
-
const helperFile =
|
|
3112
|
+
const helperFile = join5(i18nDir, "index.ts");
|
|
2699
3113
|
if (!existsSync(helperFile)) {
|
|
2700
3114
|
const helperContent = generateI18nHelper({
|
|
2701
3115
|
sourceLocale,
|
|
@@ -2710,18 +3124,18 @@ async function setupInlineI18n(cwd, componentPath, sourceLocale, targetLocales,
|
|
|
2710
3124
|
let layoutContent = await readFile5(layoutPath, "utf-8");
|
|
2711
3125
|
if (!layoutContent.includes("I18nProvider")) {
|
|
2712
3126
|
const importLines = `import { I18nProvider } from "${componentPath}";
|
|
2713
|
-
import { setServerMessages } from "${componentPath}-server";
|
|
3127
|
+
import { setServerMessages, setLocale } from "${componentPath}-server";
|
|
2714
3128
|
import { getLocale, getMessages } from "@/i18n";
|
|
2715
3129
|
`;
|
|
2716
3130
|
layoutContent = insertImportsAfterLast(layoutContent, importLines);
|
|
2717
3131
|
layoutContent = ensureAsyncLayout(layoutContent);
|
|
2718
3132
|
layoutContent = layoutContent.replace(
|
|
2719
3133
|
/return\s*\(/,
|
|
2720
|
-
"const locale = await getLocale();\n const messages = await getMessages(locale);\n setServerMessages(messages);\n\n return ("
|
|
3134
|
+
"const locale = await getLocale();\n setLocale(locale);\n const messages = await getMessages(locale);\n setServerMessages(messages);\n\n return ("
|
|
2721
3135
|
);
|
|
2722
3136
|
layoutContent = layoutContent.replace(
|
|
2723
3137
|
/(<body[^>]*>)/,
|
|
2724
|
-
"$1\n <I18nProvider messages={messages}>"
|
|
3138
|
+
"$1\n <I18nProvider messages={messages} locale={locale}>"
|
|
2725
3139
|
);
|
|
2726
3140
|
layoutContent = layoutContent.replace(
|
|
2727
3141
|
/<\/body>/,
|
|
@@ -2736,7 +3150,7 @@ import { getLocale, getMessages } from "@/i18n";
|
|
|
2736
3150
|
}
|
|
2737
3151
|
}
|
|
2738
3152
|
}
|
|
2739
|
-
await createEmptyMessageFiles(
|
|
3153
|
+
await createEmptyMessageFiles(join5(cwd, messagesDir), [
|
|
2740
3154
|
sourceLocale,
|
|
2741
3155
|
...targetLocales
|
|
2742
3156
|
]);
|
|
@@ -2746,7 +3160,7 @@ import { getLocale, getMessages } from "@/i18n";
|
|
|
2746
3160
|
}
|
|
2747
3161
|
async function runInitWizard() {
|
|
2748
3162
|
const cwd = process.cwd();
|
|
2749
|
-
const configPath =
|
|
3163
|
+
const configPath = join5(cwd, "translate-kit.config.ts");
|
|
2750
3164
|
p.intro("translate-kit setup");
|
|
2751
3165
|
if (existsSync(configPath)) {
|
|
2752
3166
|
const overwrite = await p.confirm({
|
|
@@ -2792,7 +3206,7 @@ async function runInitWizard() {
|
|
|
2792
3206
|
const sourceLocale = await p.text({
|
|
2793
3207
|
message: "Source locale:",
|
|
2794
3208
|
initialValue: "en",
|
|
2795
|
-
validate(value) {
|
|
3209
|
+
validate(value = "") {
|
|
2796
3210
|
if (!validateLocale(value)) {
|
|
2797
3211
|
return "Invalid locale. Use only letters, numbers, hyphens, and underscores.";
|
|
2798
3212
|
}
|
|
@@ -2877,7 +3291,11 @@ async function runInitWizard() {
|
|
|
2877
3291
|
await writeFile4(configPath, configContent, "utf-8");
|
|
2878
3292
|
p.log.success("Created translate-kit.config.ts");
|
|
2879
3293
|
if (mode === "inline" && componentPath) {
|
|
2880
|
-
await dropInlineComponents(cwd, componentPath
|
|
3294
|
+
await dropInlineComponents(cwd, componentPath, {
|
|
3295
|
+
sourceLocale,
|
|
3296
|
+
targetLocales,
|
|
3297
|
+
messagesDir
|
|
3298
|
+
});
|
|
2881
3299
|
await setupInlineI18n(
|
|
2882
3300
|
cwd,
|
|
2883
3301
|
componentPath,
|
|
@@ -3023,7 +3441,7 @@ init_usage();
|
|
|
3023
3441
|
init_cli_utils();
|
|
3024
3442
|
import "dotenv/config";
|
|
3025
3443
|
import { defineCommand, runMain } from "citty";
|
|
3026
|
-
import { join as
|
|
3444
|
+
import { join as join6 } from "path";
|
|
3027
3445
|
var translateCommand = defineCommand({
|
|
3028
3446
|
meta: {
|
|
3029
3447
|
name: "translate",
|
|
@@ -3075,13 +3493,13 @@ var translateCommand = defineCommand({
|
|
|
3075
3493
|
sourceFlat2[key] = text2;
|
|
3076
3494
|
}
|
|
3077
3495
|
} else {
|
|
3078
|
-
const sourceFile =
|
|
3496
|
+
const sourceFile = join6(messagesDir, `${sourceLocale}.json`);
|
|
3079
3497
|
const sourceRaw = await loadJsonFile(sourceFile);
|
|
3080
3498
|
sourceFlat2 = flatten(sourceRaw);
|
|
3081
3499
|
}
|
|
3082
3500
|
if (Object.keys(sourceFlat2).length === 0) {
|
|
3083
3501
|
logError(
|
|
3084
|
-
mode === "inline" ? `No keys found in .translate-map.json. Run 'translate-kit scan' first.` : `No keys found in ${
|
|
3502
|
+
mode === "inline" ? `No keys found in .translate-map.json. Run 'translate-kit scan' first.` : `No keys found in ${join6(messagesDir, `${sourceLocale}.json`)}`
|
|
3085
3503
|
);
|
|
3086
3504
|
process.exit(1);
|
|
3087
3505
|
}
|
|
@@ -3106,13 +3524,13 @@ var translateCommand = defineCommand({
|
|
|
3106
3524
|
sourceFlat[key] = text2;
|
|
3107
3525
|
}
|
|
3108
3526
|
} else {
|
|
3109
|
-
const sourceFile =
|
|
3527
|
+
const sourceFile = join6(messagesDir, `${sourceLocale}.json`);
|
|
3110
3528
|
const sourceRaw = await loadJsonFile(sourceFile);
|
|
3111
3529
|
sourceFlat = flatten(sourceRaw);
|
|
3112
3530
|
}
|
|
3113
3531
|
if (Object.keys(sourceFlat).length === 0) {
|
|
3114
3532
|
logError(
|
|
3115
|
-
mode === "inline" ? `No keys found in .translate-map.json. Run 'translate-kit scan' first.` : `No keys found in ${
|
|
3533
|
+
mode === "inline" ? `No keys found in .translate-map.json. Run 'translate-kit scan' first.` : `No keys found in ${join6(messagesDir, `${sourceLocale}.json`)}`
|
|
3116
3534
|
);
|
|
3117
3535
|
process.exit(1);
|
|
3118
3536
|
}
|
|
@@ -3215,7 +3633,7 @@ var scanCommand = defineCommand({
|
|
|
3215
3633
|
"Inline mode: source text stays in code, no source locale JSON created."
|
|
3216
3634
|
);
|
|
3217
3635
|
} else {
|
|
3218
|
-
const sourceFile =
|
|
3636
|
+
const sourceFile = join6(
|
|
3219
3637
|
config.messagesDir,
|
|
3220
3638
|
`${config.sourceLocale}.json`
|
|
3221
3639
|
);
|