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 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((resolve) => setTimeout(resolve, delay));
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(buildValuesObject(templateInfo.expressions, templateInfo.placeholders));
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(buildValuesObject(templateInfo.expressions, templateInfo.placeholders));
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 (!hasUseTranslationsImport(ast, importSource)) {
1295
- const importDecl = t2.importDeclaration(
1296
- [
1297
- t2.importSpecifier(
1298
- t2.identifier("useTranslations"),
1299
- t2.identifier("useTranslations")
1300
- )
1301
- ],
1302
- t2.stringLiteral(importSource)
1303
- );
1304
- const lastImportIndex = findLastImportIndex(ast);
1305
- if (lastImportIndex >= 0) {
1306
- ast.program.body.splice(lastImportIndex + 1, 0, importDecl);
1307
- } else {
1308
- ast.program.body.unshift(importDecl);
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
- traverse2(ast, {
1312
- FunctionDeclaration(path) {
1313
- const name = path.node.id?.name;
1314
- if (!name || !componentsNeedingT.has(name)) return;
1315
- injectTDeclaration(path);
1316
- },
1317
- VariableDeclarator(path) {
1318
- if (path.node.id.type !== "Identifier") return;
1319
- const name = path.node.id.name;
1320
- if (!componentsNeedingT.has(name)) return;
1321
- const init = path.node.init;
1322
- if (!init) return;
1323
- if (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression") {
1324
- if (init.body.type === "BlockStatement") {
1325
- injectTIntoBlock(init.body);
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
- noScope: true
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 isClientFile(ast) {
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
- return false;
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 = isClientFile(ast);
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(templateInfo.expressions, templateInfo.placeholders)
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(templateInfo.expressions, templateInfo.placeholders)
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 limit = pLimit3(10);
1705
- let completed = 0;
1706
- const fileResults = await Promise.all(
1939
+ const parseLimit = pLimit3(10);
1940
+ const parsedEntries = await Promise.all(
1707
1941
  files.map(
1708
- (filePath) => limit(async () => {
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}: ${err instanceof Error ? err.message : String(err)}`
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 result = transform(ast, options.textToKey, transformOpts);
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 { modified: true, wrapped: result.stringsWrapped, skipped: false };
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((resolve) => setTimeout(resolve, delay));
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 join2, dirname } from "path";
2256
+ import { join as join3, dirname as dirname2 } from "path";
1978
2257
  async function writeTranslation(filePath, flatEntries, options) {
1979
- await mkdir(dirname(filePath), { recursive: true });
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 = join2(messagesDir, ".translate-lock.json");
1997
- await mkdir(dirname(lockPath), { recursive: true });
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 join3 } from "path";
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 = join3(messagesDir, ".translate-map.json");
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 = join3(messagesDir, ".translate-map.json");
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 = join3(
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 = join3(
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 = join3(config.messagesDir, `${locale}.json`);
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
- return `import type { ReactNode } from "react";
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 join4, relative } from "path";
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(join4(cwd, "app")))
2845
+ if (existsSync(join5(cwd, "app")))
2432
2846
  patterns.push("app/**/*.tsx", "app/**/*.jsx");
2433
- if (existsSync(join4(cwd, "src")))
2847
+ if (existsSync(join5(cwd, "src")))
2434
2848
  patterns.push("src/**/*.tsx", "src/**/*.jsx");
2435
- if (existsSync(join4(cwd, "pages")))
2849
+ if (existsSync(join5(cwd, "pages")))
2436
2850
  patterns.push("pages/**/*.tsx", "pages/**/*.jsx");
2437
- if (existsSync(join4(cwd, "src", "app"))) {
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(join4(dir, "node_modules", ...parts, "package.json"))) {
2864
+ if (existsSync(join5(dir, "node_modules", ...parts, "package.json"))) {
2451
2865
  return true;
2452
2866
  }
2453
- const parent = join4(dir, "..");
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(join4(cwd, "src", "app"));
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(join4(cwd, "src"));
2539
- return join4(cwd, useSrc ? "src" : "", rel);
2952
+ const useSrc = existsSync(join5(cwd, "src"));
2953
+ return join5(cwd, useSrc ? "src" : "", rel);
2540
2954
  }
2541
2955
  if (componentPath.startsWith("~/")) {
2542
- return join4(cwd, componentPath.slice(2));
2956
+ return join5(cwd, componentPath.slice(2));
2543
2957
  }
2544
- return join4(cwd, componentPath);
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 = join4(base, "app", `layout.${ext}`);
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 = join4(msgDir, `${locale}.json`);
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 ? join4(cwd, "src") : cwd;
2990
+ const base = useSrc ? join5(cwd, "src") : cwd;
2577
2991
  const allLocales = [sourceLocale, ...targetLocales];
2578
2992
  const filesCreated = [];
2579
- const i18nDir = join4(base, "i18n");
2993
+ const i18nDir = join5(base, "i18n");
2580
2994
  await mkdir3(i18nDir, { recursive: true });
2581
- const requestFile = join4(i18nDir, "request.ts");
2995
+ const requestFile = join5(i18nDir, "request.ts");
2582
2996
  if (!existsSync(requestFile)) {
2583
- const relMessages = relative(i18nDir, join4(cwd, messagesDir));
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 = join4(cwd, "next.config.ts");
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(join4(cwd, messagesDir), allLocales);
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 = join4(fsPath, "..");
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(join4(cwd, "src"));
2694
- const base = useSrc ? join4(cwd, "src") : cwd;
3107
+ const useSrc = existsSync(join5(cwd, "src"));
3108
+ const base = useSrc ? join5(cwd, "src") : cwd;
2695
3109
  const filesCreated = [];
2696
- const i18nDir = join4(base, "i18n");
3110
+ const i18nDir = join5(base, "i18n");
2697
3111
  await mkdir3(i18nDir, { recursive: true });
2698
- const helperFile = join4(i18nDir, "index.ts");
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(join4(cwd, messagesDir), [
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 = join4(cwd, "translate-kit.config.ts");
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 join5 } from "path";
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 = join5(messagesDir, `${sourceLocale}.json`);
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 ${join5(messagesDir, `${sourceLocale}.json`)}`
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 = join5(messagesDir, `${sourceLocale}.json`);
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 ${join5(messagesDir, `${sourceLocale}.json`)}`
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 = join5(
3636
+ const sourceFile = join6(
3219
3637
  config.messagesDir,
3220
3638
  `${config.sourceLocale}.json`
3221
3639
  );