translate-kit 0.2.0 → 0.3.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/cli.js +491 -106
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -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,6 +2619,107 @@ 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
|
+
// Per-request cached message loading \u2014 works even when layout is cached during client-side navigation
|
|
2653
|
+
// Uses dynamic imports so this file can be safely imported from client components
|
|
2654
|
+
const getCachedMessages = cache(async (): Promise<Messages> => {
|
|
2655
|
+
const { headers } = await import("next/headers");
|
|
2656
|
+
const { readFile } = await import("node:fs/promises");
|
|
2657
|
+
const { join } = await import("node:path");
|
|
2658
|
+
|
|
2659
|
+
const h = await headers();
|
|
2660
|
+
const acceptLang = h.get("accept-language") ?? "";
|
|
2661
|
+
const locale = parseAcceptLanguage(acceptLang);
|
|
2662
|
+
if (locale === defaultLocale) return {};
|
|
2663
|
+
try {
|
|
2664
|
+
const filePath = join(process.cwd(), messagesDir, \`\${locale}.json\`);
|
|
2665
|
+
const content = await readFile(filePath, "utf-8");
|
|
2666
|
+
return JSON.parse(content);
|
|
2667
|
+
} catch {
|
|
2668
|
+
return {};
|
|
2669
|
+
}
|
|
2670
|
+
});
|
|
2671
|
+
|
|
2672
|
+
// Per-request message store (populated by setServerMessages in layout)
|
|
2673
|
+
const getMessageStore = cache(() => ({ current: null as Messages | null }));
|
|
2674
|
+
|
|
2675
|
+
export function setServerMessages(messages: Messages) {
|
|
2676
|
+
getMessageStore().current = messages;
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
async function resolveMessages(explicit?: Messages): Promise<Messages> {
|
|
2680
|
+
if (explicit) return explicit;
|
|
2681
|
+
const store = getMessageStore().current;
|
|
2682
|
+
if (store) return store;
|
|
2683
|
+
return getCachedMessages();
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
export async function T({ id, children, messages }: { id?: string; children: ReactNode; messages?: Messages }) {
|
|
2687
|
+
if (!id) return <>{children}</>;
|
|
2688
|
+
const msgs = await resolveMessages(messages);
|
|
2689
|
+
// Populate store so sync createT() calls in the same request benefit
|
|
2690
|
+
if (!messages && !getMessageStore().current) {
|
|
2691
|
+
getMessageStore().current = msgs;
|
|
2692
|
+
}
|
|
2693
|
+
return <>{msgs[id] ?? children}</>;
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
type TFn = (text: string, id?: string, values?: Record<string, string | number>) => string;
|
|
2697
|
+
|
|
2698
|
+
// Backward-compatible: works both as sync createT() and async await createT()
|
|
2699
|
+
// - Sync: reads from store (works when layout called setServerMessages)
|
|
2700
|
+
// - Async: lazily loads messages from filesystem (works during client-side navigation)
|
|
2701
|
+
export function createT(messages?: Messages): TFn & PromiseLike<TFn> {
|
|
2702
|
+
const t: TFn = (text, id, values) => {
|
|
2703
|
+
const msgs = messages ?? getMessageStore().current ?? {};
|
|
2704
|
+
const raw = id ? (msgs[id] ?? text) : text;
|
|
2705
|
+
if (!values) return raw;
|
|
2706
|
+
return raw.replace(/\\{(\\w+)\\}/g, (_, k) => String(values[k] ?? \`{\${k}}\`));
|
|
2707
|
+
};
|
|
2708
|
+
|
|
2709
|
+
const asyncResult = resolveMessages(messages).then(msgs => {
|
|
2710
|
+
if (!messages && !getMessageStore().current) {
|
|
2711
|
+
getMessageStore().current = msgs;
|
|
2712
|
+
}
|
|
2713
|
+
const bound: TFn = (text, id, values) => {
|
|
2714
|
+
const raw = id ? (msgs[id] ?? text) : text;
|
|
2715
|
+
if (!values) return raw;
|
|
2716
|
+
return raw.replace(/\\{(\\w+)\\}/g, (_, k) => String(values[k] ?? \`{\${k}}\`));
|
|
2717
|
+
};
|
|
2718
|
+
return bound;
|
|
2719
|
+
});
|
|
2720
|
+
|
|
2721
|
+
return Object.assign(t, { then: asyncResult.then.bind(asyncResult) });
|
|
2722
|
+
}
|
|
2342
2723
|
`;
|
|
2343
2724
|
}
|
|
2344
2725
|
function generateI18nHelper(opts) {
|
|
@@ -2424,17 +2805,17 @@ __export(init_exports, {
|
|
|
2424
2805
|
});
|
|
2425
2806
|
import * as p from "@clack/prompts";
|
|
2426
2807
|
import { existsSync } from "fs";
|
|
2427
|
-
import { basename, join as
|
|
2808
|
+
import { basename, join as join5, relative } from "path";
|
|
2428
2809
|
import { readFile as readFile5, writeFile as writeFile4, mkdir as mkdir3 } from "fs/promises";
|
|
2429
2810
|
function detectIncludePatterns(cwd) {
|
|
2430
2811
|
const patterns = [];
|
|
2431
|
-
if (existsSync(
|
|
2812
|
+
if (existsSync(join5(cwd, "app")))
|
|
2432
2813
|
patterns.push("app/**/*.tsx", "app/**/*.jsx");
|
|
2433
|
-
if (existsSync(
|
|
2814
|
+
if (existsSync(join5(cwd, "src")))
|
|
2434
2815
|
patterns.push("src/**/*.tsx", "src/**/*.jsx");
|
|
2435
|
-
if (existsSync(
|
|
2816
|
+
if (existsSync(join5(cwd, "pages")))
|
|
2436
2817
|
patterns.push("pages/**/*.tsx", "pages/**/*.jsx");
|
|
2437
|
-
if (existsSync(
|
|
2818
|
+
if (existsSync(join5(cwd, "src", "app"))) {
|
|
2438
2819
|
return patterns.filter((p2) => !p2.startsWith("app/"));
|
|
2439
2820
|
}
|
|
2440
2821
|
return patterns.length > 0 ? patterns : ["**/*.tsx", "**/*.jsx"];
|
|
@@ -2447,10 +2828,10 @@ function findPackageInNodeModules(cwd, pkg) {
|
|
|
2447
2828
|
let dir = cwd;
|
|
2448
2829
|
const parts = pkg.split("/");
|
|
2449
2830
|
while (true) {
|
|
2450
|
-
if (existsSync(
|
|
2831
|
+
if (existsSync(join5(dir, "node_modules", ...parts, "package.json"))) {
|
|
2451
2832
|
return true;
|
|
2452
2833
|
}
|
|
2453
|
-
const parent =
|
|
2834
|
+
const parent = join5(dir, "..");
|
|
2454
2835
|
if (parent === dir) break;
|
|
2455
2836
|
dir = parent;
|
|
2456
2837
|
}
|
|
@@ -2530,22 +2911,22 @@ async function safeWriteModifiedFile(filePath, modified, label) {
|
|
|
2530
2911
|
return true;
|
|
2531
2912
|
}
|
|
2532
2913
|
function detectSrcDir(cwd) {
|
|
2533
|
-
return existsSync(
|
|
2914
|
+
return existsSync(join5(cwd, "src", "app"));
|
|
2534
2915
|
}
|
|
2535
2916
|
function resolveComponentPath(cwd, componentPath) {
|
|
2536
2917
|
if (componentPath.startsWith("@/")) {
|
|
2537
2918
|
const rel = componentPath.slice(2);
|
|
2538
|
-
const useSrc = existsSync(
|
|
2539
|
-
return
|
|
2919
|
+
const useSrc = existsSync(join5(cwd, "src"));
|
|
2920
|
+
return join5(cwd, useSrc ? "src" : "", rel);
|
|
2540
2921
|
}
|
|
2541
2922
|
if (componentPath.startsWith("~/")) {
|
|
2542
|
-
return
|
|
2923
|
+
return join5(cwd, componentPath.slice(2));
|
|
2543
2924
|
}
|
|
2544
|
-
return
|
|
2925
|
+
return join5(cwd, componentPath);
|
|
2545
2926
|
}
|
|
2546
2927
|
function findLayoutFile(base) {
|
|
2547
2928
|
for (const ext of ["tsx", "jsx", "ts", "js"]) {
|
|
2548
|
-
const candidate =
|
|
2929
|
+
const candidate = join5(base, "app", `layout.${ext}`);
|
|
2549
2930
|
if (existsSync(candidate)) return candidate;
|
|
2550
2931
|
}
|
|
2551
2932
|
return void 0;
|
|
@@ -2553,7 +2934,7 @@ function findLayoutFile(base) {
|
|
|
2553
2934
|
async function createEmptyMessageFiles(msgDir, locales) {
|
|
2554
2935
|
await mkdir3(msgDir, { recursive: true });
|
|
2555
2936
|
for (const locale of locales) {
|
|
2556
|
-
const msgFile =
|
|
2937
|
+
const msgFile = join5(msgDir, `${locale}.json`);
|
|
2557
2938
|
if (!existsSync(msgFile)) {
|
|
2558
2939
|
await writeFile4(msgFile, "{}\n", "utf-8");
|
|
2559
2940
|
}
|
|
@@ -2573,14 +2954,14 @@ function ensureAsyncLayout(content) {
|
|
|
2573
2954
|
}
|
|
2574
2955
|
async function setupNextIntl(cwd, sourceLocale, targetLocales, messagesDir) {
|
|
2575
2956
|
const useSrc = detectSrcDir(cwd);
|
|
2576
|
-
const base = useSrc ?
|
|
2957
|
+
const base = useSrc ? join5(cwd, "src") : cwd;
|
|
2577
2958
|
const allLocales = [sourceLocale, ...targetLocales];
|
|
2578
2959
|
const filesCreated = [];
|
|
2579
|
-
const i18nDir =
|
|
2960
|
+
const i18nDir = join5(base, "i18n");
|
|
2580
2961
|
await mkdir3(i18nDir, { recursive: true });
|
|
2581
|
-
const requestFile =
|
|
2962
|
+
const requestFile = join5(i18nDir, "request.ts");
|
|
2582
2963
|
if (!existsSync(requestFile)) {
|
|
2583
|
-
const relMessages = relative(i18nDir,
|
|
2964
|
+
const relMessages = relative(i18nDir, join5(cwd, messagesDir));
|
|
2584
2965
|
const allLocalesStr = allLocales.map((l) => `"${l}"`).join(", ");
|
|
2585
2966
|
await writeFile4(
|
|
2586
2967
|
requestFile,
|
|
@@ -2621,7 +3002,7 @@ export default getRequestConfig(async () => {
|
|
|
2621
3002
|
);
|
|
2622
3003
|
filesCreated.push(relative(cwd, requestFile));
|
|
2623
3004
|
}
|
|
2624
|
-
const nextConfigPath =
|
|
3005
|
+
const nextConfigPath = join5(cwd, "next.config.ts");
|
|
2625
3006
|
if (existsSync(nextConfigPath)) {
|
|
2626
3007
|
const content = await readFile5(nextConfigPath, "utf-8");
|
|
2627
3008
|
if (!content.includes("next-intl")) {
|
|
@@ -2671,31 +3052,31 @@ export default getRequestConfig(async () => {
|
|
|
2671
3052
|
}
|
|
2672
3053
|
}
|
|
2673
3054
|
}
|
|
2674
|
-
await createEmptyMessageFiles(
|
|
3055
|
+
await createEmptyMessageFiles(join5(cwd, messagesDir), allLocales);
|
|
2675
3056
|
if (filesCreated.length > 0) {
|
|
2676
3057
|
p.log.success(`next-intl configured: ${filesCreated.join(", ")}`);
|
|
2677
3058
|
}
|
|
2678
3059
|
}
|
|
2679
|
-
async function dropInlineComponents(cwd, componentPath) {
|
|
3060
|
+
async function dropInlineComponents(cwd, componentPath, localeOpts) {
|
|
2680
3061
|
const fsPath = resolveComponentPath(cwd, componentPath);
|
|
2681
|
-
const dir =
|
|
3062
|
+
const dir = join5(fsPath, "..");
|
|
2682
3063
|
await mkdir3(dir, { recursive: true });
|
|
2683
3064
|
const clientFile = `${fsPath}.tsx`;
|
|
2684
3065
|
const serverFile = `${fsPath}-server.tsx`;
|
|
2685
3066
|
const clientBasename = basename(fsPath);
|
|
2686
3067
|
await writeFile4(clientFile, CLIENT_TEMPLATE, "utf-8");
|
|
2687
|
-
await writeFile4(serverFile, serverTemplate(clientBasename), "utf-8");
|
|
3068
|
+
await writeFile4(serverFile, serverTemplate(clientBasename, localeOpts), "utf-8");
|
|
2688
3069
|
const relClient = relative(cwd, clientFile);
|
|
2689
3070
|
const relServer = relative(cwd, serverFile);
|
|
2690
3071
|
p.log.success(`Created inline components: ${relClient}, ${relServer}`);
|
|
2691
3072
|
}
|
|
2692
3073
|
async function setupInlineI18n(cwd, componentPath, sourceLocale, targetLocales, messagesDir) {
|
|
2693
|
-
const useSrc = existsSync(
|
|
2694
|
-
const base = useSrc ?
|
|
3074
|
+
const useSrc = existsSync(join5(cwd, "src"));
|
|
3075
|
+
const base = useSrc ? join5(cwd, "src") : cwd;
|
|
2695
3076
|
const filesCreated = [];
|
|
2696
|
-
const i18nDir =
|
|
3077
|
+
const i18nDir = join5(base, "i18n");
|
|
2697
3078
|
await mkdir3(i18nDir, { recursive: true });
|
|
2698
|
-
const helperFile =
|
|
3079
|
+
const helperFile = join5(i18nDir, "index.ts");
|
|
2699
3080
|
if (!existsSync(helperFile)) {
|
|
2700
3081
|
const helperContent = generateI18nHelper({
|
|
2701
3082
|
sourceLocale,
|
|
@@ -2736,7 +3117,7 @@ import { getLocale, getMessages } from "@/i18n";
|
|
|
2736
3117
|
}
|
|
2737
3118
|
}
|
|
2738
3119
|
}
|
|
2739
|
-
await createEmptyMessageFiles(
|
|
3120
|
+
await createEmptyMessageFiles(join5(cwd, messagesDir), [
|
|
2740
3121
|
sourceLocale,
|
|
2741
3122
|
...targetLocales
|
|
2742
3123
|
]);
|
|
@@ -2746,7 +3127,7 @@ import { getLocale, getMessages } from "@/i18n";
|
|
|
2746
3127
|
}
|
|
2747
3128
|
async function runInitWizard() {
|
|
2748
3129
|
const cwd = process.cwd();
|
|
2749
|
-
const configPath =
|
|
3130
|
+
const configPath = join5(cwd, "translate-kit.config.ts");
|
|
2750
3131
|
p.intro("translate-kit setup");
|
|
2751
3132
|
if (existsSync(configPath)) {
|
|
2752
3133
|
const overwrite = await p.confirm({
|
|
@@ -2877,7 +3258,11 @@ async function runInitWizard() {
|
|
|
2877
3258
|
await writeFile4(configPath, configContent, "utf-8");
|
|
2878
3259
|
p.log.success("Created translate-kit.config.ts");
|
|
2879
3260
|
if (mode === "inline" && componentPath) {
|
|
2880
|
-
await dropInlineComponents(cwd, componentPath
|
|
3261
|
+
await dropInlineComponents(cwd, componentPath, {
|
|
3262
|
+
sourceLocale,
|
|
3263
|
+
targetLocales,
|
|
3264
|
+
messagesDir
|
|
3265
|
+
});
|
|
2881
3266
|
await setupInlineI18n(
|
|
2882
3267
|
cwd,
|
|
2883
3268
|
componentPath,
|
|
@@ -3023,7 +3408,7 @@ init_usage();
|
|
|
3023
3408
|
init_cli_utils();
|
|
3024
3409
|
import "dotenv/config";
|
|
3025
3410
|
import { defineCommand, runMain } from "citty";
|
|
3026
|
-
import { join as
|
|
3411
|
+
import { join as join6 } from "path";
|
|
3027
3412
|
var translateCommand = defineCommand({
|
|
3028
3413
|
meta: {
|
|
3029
3414
|
name: "translate",
|
|
@@ -3075,13 +3460,13 @@ var translateCommand = defineCommand({
|
|
|
3075
3460
|
sourceFlat2[key] = text2;
|
|
3076
3461
|
}
|
|
3077
3462
|
} else {
|
|
3078
|
-
const sourceFile =
|
|
3463
|
+
const sourceFile = join6(messagesDir, `${sourceLocale}.json`);
|
|
3079
3464
|
const sourceRaw = await loadJsonFile(sourceFile);
|
|
3080
3465
|
sourceFlat2 = flatten(sourceRaw);
|
|
3081
3466
|
}
|
|
3082
3467
|
if (Object.keys(sourceFlat2).length === 0) {
|
|
3083
3468
|
logError(
|
|
3084
|
-
mode === "inline" ? `No keys found in .translate-map.json. Run 'translate-kit scan' first.` : `No keys found in ${
|
|
3469
|
+
mode === "inline" ? `No keys found in .translate-map.json. Run 'translate-kit scan' first.` : `No keys found in ${join6(messagesDir, `${sourceLocale}.json`)}`
|
|
3085
3470
|
);
|
|
3086
3471
|
process.exit(1);
|
|
3087
3472
|
}
|
|
@@ -3106,13 +3491,13 @@ var translateCommand = defineCommand({
|
|
|
3106
3491
|
sourceFlat[key] = text2;
|
|
3107
3492
|
}
|
|
3108
3493
|
} else {
|
|
3109
|
-
const sourceFile =
|
|
3494
|
+
const sourceFile = join6(messagesDir, `${sourceLocale}.json`);
|
|
3110
3495
|
const sourceRaw = await loadJsonFile(sourceFile);
|
|
3111
3496
|
sourceFlat = flatten(sourceRaw);
|
|
3112
3497
|
}
|
|
3113
3498
|
if (Object.keys(sourceFlat).length === 0) {
|
|
3114
3499
|
logError(
|
|
3115
|
-
mode === "inline" ? `No keys found in .translate-map.json. Run 'translate-kit scan' first.` : `No keys found in ${
|
|
3500
|
+
mode === "inline" ? `No keys found in .translate-map.json. Run 'translate-kit scan' first.` : `No keys found in ${join6(messagesDir, `${sourceLocale}.json`)}`
|
|
3116
3501
|
);
|
|
3117
3502
|
process.exit(1);
|
|
3118
3503
|
}
|
|
@@ -3215,7 +3600,7 @@ var scanCommand = defineCommand({
|
|
|
3215
3600
|
"Inline mode: source text stays in code, no source locale JSON created."
|
|
3216
3601
|
);
|
|
3217
3602
|
} else {
|
|
3218
|
-
const sourceFile =
|
|
3603
|
+
const sourceFile = join6(
|
|
3219
3604
|
config.messagesDir,
|
|
3220
3605
|
`${config.sourceLocale}.json`
|
|
3221
3606
|
);
|