livelocale 0.8.0 → 0.9.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.
Files changed (53) hide show
  1. package/README.md +230 -5
  2. package/dist/client/index.cjs +178 -70
  3. package/dist/client/index.cjs.map +1 -1
  4. package/dist/client/index.d.cts +36 -22
  5. package/dist/client/index.d.ts +36 -22
  6. package/dist/client/index.js +177 -70
  7. package/dist/client/index.js.map +1 -1
  8. package/dist/codemod/index.cjs +3057 -152
  9. package/dist/codemod/index.cjs.map +1 -1
  10. package/dist/codemod/index.js +3058 -153
  11. package/dist/codemod/index.js.map +1 -1
  12. package/dist/react/index.cjs +185 -77
  13. package/dist/react/index.cjs.map +1 -1
  14. package/dist/react/index.js +185 -77
  15. package/dist/react/index.js.map +1 -1
  16. package/dist/server/index.cjs +108 -66
  17. package/dist/server/index.cjs.map +1 -1
  18. package/dist/server/index.d.cts +13 -3
  19. package/dist/server/index.d.ts +13 -3
  20. package/dist/server/index.js +107 -66
  21. package/dist/server/index.js.map +1 -1
  22. package/dist/studio/assets/index-Bvbtkry-.css +1 -0
  23. package/dist/studio/assets/index-DC_jRsox.js +13 -0
  24. package/dist/studio/assets/inter-latin-300-normal-BVlfKGgI.woff2 +0 -0
  25. package/dist/studio/assets/inter-latin-300-normal-i8F0SvXL.woff +0 -0
  26. package/dist/studio/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
  27. package/dist/studio/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
  28. package/dist/studio/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
  29. package/dist/studio/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
  30. package/dist/studio/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
  31. package/dist/studio/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
  32. package/dist/studio/assets/inter-latin-700-normal-BLAVimhd.woff +0 -0
  33. package/dist/studio/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
  34. package/dist/studio/assets/outfit-latin-400-normal-BGsTXAXT.woff2 +0 -0
  35. package/dist/studio/assets/outfit-latin-400-normal-DMwTpYkH.woff +0 -0
  36. package/dist/studio/assets/outfit-latin-500-normal-ClnHRwRh.woff +0 -0
  37. package/dist/studio/assets/outfit-latin-500-normal-DKnIMDSk.woff2 +0 -0
  38. package/dist/studio/assets/outfit-latin-600-normal-B7SfZ07L.woff2 +0 -0
  39. package/dist/studio/assets/outfit-latin-600-normal-BEfTtDA7.woff +0 -0
  40. package/dist/studio/assets/outfit-latin-700-normal-Cu9v6i1X.woff2 +0 -0
  41. package/dist/studio/assets/outfit-latin-700-normal-D4itBLBr.woff +0 -0
  42. package/dist/studio/index.html +3 -6
  43. package/dist/vite/index.cjs +739 -77
  44. package/dist/vite/index.cjs.map +1 -1
  45. package/dist/vite/index.d.cts +22 -2
  46. package/dist/vite/index.d.ts +22 -2
  47. package/dist/vite/index.js +738 -77
  48. package/dist/vite/index.js.map +1 -1
  49. package/dist/{wrap-CsOtHp8r.d.cts → wrap-2qVxLN9Q.d.cts} +10 -18
  50. package/dist/{wrap-CsOtHp8r.d.ts → wrap-2qVxLN9Q.d.ts} +10 -18
  51. package/package.json +4 -1
  52. package/dist/studio/assets/index-D7ifnqy5.js +0 -10
  53. package/dist/studio/assets/index-Zh0KtFUH.css +0 -2
@@ -1,6 +1,6 @@
1
1
  // src/server/vite-plugin.ts
2
- import * as path5 from "path";
3
- import * as fs4 from "fs";
2
+ import * as path9 from "path";
3
+ import * as fs5 from "fs";
4
4
 
5
5
  // src/server/file-mutator.ts
6
6
  import { promises as fs2 } from "fs";
@@ -526,14 +526,6 @@ var KeyScanner = class {
526
526
  };
527
527
 
528
528
  // src/server/file-mutator.ts
529
- function assertEnabled(component) {
530
- const reason = getDisabledReason();
531
- if (reason !== null) {
532
- throw new Error(
533
- `[livelocale] ${component} can only be executed in development mode (${reason}).`
534
- );
535
- }
536
- }
537
529
  var FileMutex = class {
538
530
  queue = Promise.resolve();
539
531
  async acquire(fn) {
@@ -549,7 +541,23 @@ var FileMutex = class {
549
541
  };
550
542
  var FileMutator = class {
551
543
  fileLocks = /* @__PURE__ */ new Map();
552
- scanner = new KeyScanner();
544
+ requireEnabled;
545
+ quiet;
546
+ scanner;
547
+ constructor(options = {}) {
548
+ this.requireEnabled = options.requireEnabled ?? true;
549
+ this.quiet = options.quiet ?? false;
550
+ this.scanner = new KeyScanner({ requireEnabled: this.requireEnabled });
551
+ }
552
+ assertEnabled() {
553
+ if (!this.requireEnabled) return;
554
+ const reason = getDisabledReason();
555
+ if (reason !== null) {
556
+ throw new Error(
557
+ `[livelocale] FileMutator can only be executed in development mode (${reason}).`
558
+ );
559
+ }
560
+ }
553
561
  /**
554
562
  * Helper to serialize execution per file path
555
563
  */
@@ -567,7 +575,7 @@ var FileMutator = class {
567
575
  * structural JSON validation, and concurrency serialization.
568
576
  */
569
577
  async updateLocaleKey(basePath, locale, keyPath, newValue) {
570
- assertEnabled("FileMutator");
578
+ this.assertEnabled();
571
579
  if (!isSafeLocaleSegment(locale)) {
572
580
  throw new Error(
573
581
  `[livelocale] Path traversal attempt detected in locale identifier: "${locale}"`
@@ -670,7 +678,9 @@ var FileMutator = class {
670
678
  `[livelocale] Failed to rename temp file to target at ${filePath}. Operation: rename. Original error: ${err.message}`
671
679
  );
672
680
  }
673
- console.log(`[livelocale] Updated key '${keyPath}' in locale '${locale}'`);
681
+ if (!this.quiet) {
682
+ console.log(`[livelocale] Updated key '${keyPath}' in locale '${locale}'`);
683
+ }
674
684
  });
675
685
  }
676
686
  /**
@@ -678,7 +688,7 @@ var FileMutator = class {
678
688
  * `*.json` inside the `{locale}/` folder merged under its namespace.
679
689
  */
680
690
  async readLocale(basePath, locale) {
681
- assertEnabled("FileMutator");
691
+ this.assertEnabled();
682
692
  const localePath = path3.resolve(basePath, locale);
683
693
  let isDir = false;
684
694
  try {
@@ -705,7 +715,7 @@ var FileMutator = class {
705
715
  * Lists all locale codes dynamically scanned from the locales directory.
706
716
  */
707
717
  async listLocales(basePath) {
708
- assertEnabled("FileMutator");
718
+ this.assertEnabled();
709
719
  const resolvedBasePath = path3.resolve(basePath);
710
720
  const items = await fs2.readdir(resolvedBasePath, { withFileTypes: true });
711
721
  const localesSet = /* @__PURE__ */ new Set();
@@ -727,7 +737,7 @@ var FileMutator = class {
727
737
  * Adds a new locale directory/file based on current repository structure.
728
738
  */
729
739
  async addLocale(basePath, locale) {
730
- assertEnabled("FileMutator");
740
+ this.assertEnabled();
731
741
  if (!isSafeLocaleSegment(locale)) {
732
742
  throw new Error(
733
743
  `[livelocale] Path traversal attempt detected in locale identifier: "${locale}"`
@@ -783,7 +793,7 @@ var FileMutator = class {
783
793
  * Renames an existing locale (directory or file) to a new name.
784
794
  */
785
795
  async renameLocale(basePath, oldLocale, newLocale) {
786
- assertEnabled("FileMutator");
796
+ this.assertEnabled();
787
797
  if (!isSafeLocaleSegment(oldLocale) || !isSafeLocaleSegment(newLocale)) {
788
798
  throw new Error("[livelocale] Path traversal attempt detected");
789
799
  }
@@ -841,7 +851,7 @@ var FileMutator = class {
841
851
  * Deletes an existing locale (directory or file).
842
852
  */
843
853
  async deleteLocale(basePath, locale) {
844
- assertEnabled("FileMutator");
854
+ this.assertEnabled();
845
855
  if (!isSafeLocaleSegment(locale)) {
846
856
  throw new Error("[livelocale] Path traversal attempt detected");
847
857
  }
@@ -1204,6 +1214,67 @@ function validateMessageSyntax(message) {
1204
1214
  return parseIcu(prepared).error;
1205
1215
  }
1206
1216
 
1217
+ // src/core/save-rules.ts
1218
+ var reject = (status, code, error) => ({
1219
+ ok: false,
1220
+ status,
1221
+ code,
1222
+ error
1223
+ });
1224
+ function validateWrite(input, options = {}) {
1225
+ const { locale, key, value } = input;
1226
+ if (!isValidLocaleFormat(locale)) {
1227
+ return reject(400, "INVALID_LOCALE_FORMAT", "Invalid locale string parameter.");
1228
+ }
1229
+ if (!isValidKeyFormat(key)) {
1230
+ return reject(
1231
+ 400,
1232
+ "INVALID_KEY_FORMAT",
1233
+ "Invalid key path. Must be 1-200 characters containing alphanumeric, dots, underscores, or hyphens."
1234
+ );
1235
+ }
1236
+ if (!isSafeKeyPath(key)) {
1237
+ return reject(
1238
+ 400,
1239
+ "INVALID_KEY_FORMAT",
1240
+ "Forbidden key path: segments like __proto__, constructor, or prototype are not allowed."
1241
+ );
1242
+ }
1243
+ if (!isValidValueFormat(value)) {
1244
+ return reject(
1245
+ 400,
1246
+ "INVALID_VALUE_FORMAT",
1247
+ "Invalid value. Must be a string less than 10000 characters."
1248
+ );
1249
+ }
1250
+ const accepted = {
1251
+ ok: true,
1252
+ locale,
1253
+ key,
1254
+ value
1255
+ };
1256
+ if (options.force === true) return accepted;
1257
+ const syntaxError = validateMessageSyntax(accepted.value);
1258
+ if (syntaxError) {
1259
+ return reject(422, "MESSAGE_SYNTAX", `Message syntax: ${syntaxError}.`);
1260
+ }
1261
+ const comparison = comparePlaceholders(options.reference, accepted.value);
1262
+ if (!comparison.ok) {
1263
+ const parts = [];
1264
+ if (comparison.missing.length) parts.push(`missing ${comparison.missing.join(", ")}`);
1265
+ if (comparison.extra.length) parts.push(`unexpected ${comparison.extra.join(", ")}`);
1266
+ return {
1267
+ ...reject(
1268
+ 422,
1269
+ "PLACEHOLDER_MISMATCH",
1270
+ `Placeholders differ from the reference: ${parts.join("; ")}.`
1271
+ ),
1272
+ details: { missing: comparison.missing, extra: comparison.extra }
1273
+ };
1274
+ }
1275
+ return accepted;
1276
+ }
1277
+
1207
1278
  // src/server/router.ts
1208
1279
  var DEFAULT_ALLOWED_ORIGINS = ["http://localhost:3010", "http://127.0.0.1:3010"];
1209
1280
  var MAX_BODY_BYTES = 1e6;
@@ -1391,53 +1462,18 @@ function createMutationRouter(mutator, options) {
1391
1462
  await mutator.deleteLocale(basePath, locale);
1392
1463
  return json(200, headers, { success: true, locale });
1393
1464
  }
1394
- if (!isValidLocaleFormat(locale)) {
1395
- return json(400, headers, {
1396
- error: "Invalid locale string parameter.",
1397
- code: "INVALID_LOCALE_FORMAT"
1398
- });
1399
- }
1400
- if (!isValidKeyFormat(key)) {
1401
- return json(400, headers, {
1402
- error: "Invalid key path. Must be 1-200 characters containing alphanumeric, dots, underscores, or hyphens.",
1403
- code: "INVALID_KEY_FORMAT"
1404
- });
1405
- }
1406
- if (!isSafeKeyPath(key)) {
1407
- return json(400, headers, {
1408
- error: "Forbidden key path: segments like __proto__, constructor, or prototype are not allowed.",
1409
- code: "INVALID_KEY_FORMAT"
1410
- });
1411
- }
1412
- if (!isValidValueFormat(value)) {
1413
- return json(400, headers, {
1414
- error: "Invalid value. Must be a string less than 10000 characters.",
1415
- code: "INVALID_VALUE_FORMAT"
1465
+ const verdict = validateWrite(
1466
+ { locale, key, value },
1467
+ { reference: typeof reference === "string" ? reference : null, force: force === true }
1468
+ );
1469
+ if (!verdict.ok) {
1470
+ return json(verdict.status, headers, {
1471
+ error: verdict.error,
1472
+ code: verdict.code,
1473
+ details: verdict.details
1416
1474
  });
1417
1475
  }
1418
- if (force !== true) {
1419
- const syntaxError = validateMessageSyntax(value);
1420
- if (syntaxError) {
1421
- return json(422, headers, {
1422
- error: `Message syntax: ${syntaxError}.`,
1423
- code: "MESSAGE_SYNTAX"
1424
- });
1425
- }
1426
- if (typeof reference === "string") {
1427
- const comparison = comparePlaceholders(reference, value);
1428
- if (!comparison.ok) {
1429
- const parts = [];
1430
- if (comparison.missing.length) parts.push(`missing ${comparison.missing.join(", ")}`);
1431
- if (comparison.extra.length) parts.push(`unexpected ${comparison.extra.join(", ")}`);
1432
- return json(422, headers, {
1433
- error: `Placeholders differ from the reference: ${parts.join("; ")}.`,
1434
- code: "PLACEHOLDER_MISMATCH",
1435
- details: { missing: comparison.missing, extra: comparison.extra }
1436
- });
1437
- }
1438
- }
1439
- }
1440
- await mutator.updateLocaleKey(basePath, locale, key, value);
1476
+ await mutator.updateLocaleKey(basePath, verdict.locale, verdict.key, verdict.value);
1441
1477
  return json(200, headers, { success: true, key, locale });
1442
1478
  }
1443
1479
  return {
@@ -1511,7 +1547,7 @@ var BodyTooLargeError = class extends Error {
1511
1547
  }
1512
1548
  };
1513
1549
  function readNodeBody(req, maxBytes) {
1514
- return new Promise((resolve6, reject) => {
1550
+ return new Promise((resolve8, reject2) => {
1515
1551
  let bodyText = "";
1516
1552
  let settled = false;
1517
1553
  req.on("data", (chunk) => {
@@ -1519,19 +1555,19 @@ function readNodeBody(req, maxBytes) {
1519
1555
  bodyText += chunk.toString();
1520
1556
  if (Buffer.byteLength(bodyText, "utf-8") > maxBytes) {
1521
1557
  settled = true;
1522
- reject(new BodyTooLargeError());
1558
+ reject2(new BodyTooLargeError());
1523
1559
  req.destroy?.();
1524
1560
  }
1525
1561
  });
1526
1562
  req.on("end", () => {
1527
1563
  if (settled) return;
1528
1564
  settled = true;
1529
- resolve6(bodyText);
1565
+ resolve8(bodyText);
1530
1566
  });
1531
1567
  req.on("error", (err) => {
1532
1568
  if (settled) return;
1533
1569
  settled = true;
1534
- reject(err);
1570
+ reject2(err);
1535
1571
  });
1536
1572
  });
1537
1573
  }
@@ -1551,6 +1587,590 @@ function deepMerge(target, source) {
1551
1587
  return result;
1552
1588
  }
1553
1589
 
1590
+ // src/server/pack.ts
1591
+ import { existsSync as existsSync3, readFileSync as readFileSync4, statSync, promises as fs4 } from "fs";
1592
+ import * as path8 from "path";
1593
+
1594
+ // src/core/flag-script.ts
1595
+ var FLAG_SCRIPT_SIGNATURE = "/*! livelocale flag v1 */";
1596
+ var FLAG_SCRIPT = `${FLAG_SCRIPT_SIGNATURE}
1597
+ (function () {
1598
+ try {
1599
+ var me = document.currentScript;
1600
+ if (!me || !me.src || window.parent === window || window.__LIVELOCALE_FLAG__ !== undefined)
1601
+ return;
1602
+ var studio = new URL('.', me.src);
1603
+ var host = window.parent.location;
1604
+ if (
1605
+ host.origin === 'null' ||
1606
+ host.origin !== window.location.origin ||
1607
+ host.origin !== studio.origin
1608
+ )
1609
+ return;
1610
+ if (host.pathname.indexOf(studio.pathname) !== 0) return;
1611
+ window.__LIVELOCALE_FLAG__ = 'preview';
1612
+ } catch (e) {}
1613
+ })();
1614
+ `;
1615
+
1616
+ // src/core/change-link.ts
1617
+ function isValidRepoSlug(slug) {
1618
+ if (!/^[A-Za-z0-9][A-Za-z0-9-]{0,38}\/[A-Za-z0-9._-]{1,100}$/.test(slug)) return false;
1619
+ const repo = slug.slice(slug.indexOf("/") + 1);
1620
+ return repo !== "." && repo !== "..";
1621
+ }
1622
+ var BRANCH_NAME_RE = /^(?!-)(?!.*\.\.)[^\s\x00-\x1f\x7f]{1,255}$/;
1623
+ function isValidBranchName(name) {
1624
+ return BRANCH_NAME_RE.test(name) && !hasLoneSurrogate(name);
1625
+ }
1626
+ var LONE_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]/;
1627
+ function hasLoneSurrogate(text) {
1628
+ return typeof text === "string" && LONE_SURROGATE_RE.test(text);
1629
+ }
1630
+ var MAX_INFLATED_BYTES = 512 * 1024;
1631
+ var TOO_LARGE_MESSAGE = `A change link holds at most ${MAX_INFLATED_BYTES / 1024} KB of text. Send the changes in smaller links.`;
1632
+
1633
+ // src/core/share-manifest.ts
1634
+ var SHARE_DIR = "livelocale";
1635
+ var SHARE_MANIFEST_FILE = "manifest.json";
1636
+ var SHARE_FLAG_FILE = "flag.js";
1637
+ var SHARE_LOCALES_DIR = "locales";
1638
+ var SHARE_MANIFEST_FORMAT = 1;
1639
+
1640
+ // src/server/build-info.ts
1641
+ import { execFile as execFile2 } from "child_process";
1642
+ import { readFileSync as readFileSync2 } from "fs";
1643
+ import * as path5 from "path";
1644
+ var runGit = (args, cwd) => new Promise((resolve8) => {
1645
+ try {
1646
+ execFile2(
1647
+ "git",
1648
+ args,
1649
+ { cwd, windowsHide: true, env: { ...process.env, GIT_TERMINAL_PROMPT: "0" } },
1650
+ (error, stdout) => resolve8(error ? null : String(stdout).trim())
1651
+ );
1652
+ } catch {
1653
+ resolve8(null);
1654
+ }
1655
+ });
1656
+ var SHA_RE = /^[0-9a-f]{40}([0-9a-f]{24})?$/i;
1657
+ async function firstValid(candidates, accept) {
1658
+ for (const candidate of candidates) {
1659
+ const value = typeof candidate === "function" ? await candidate() : candidate;
1660
+ if (typeof value !== "string" || value === "") continue;
1661
+ const accepted = accept(value);
1662
+ if (accepted !== null) return accepted;
1663
+ }
1664
+ return null;
1665
+ }
1666
+ var asBranch = (value) => isValidBranchName(value) ? value : null;
1667
+ var asSha = (value) => SHA_RE.test(value) ? value.toLowerCase() : null;
1668
+ var asSlug = (value) => isValidRepoSlug(value) ? value : null;
1669
+ function readEvent(eventPath) {
1670
+ if (!eventPath) return {};
1671
+ try {
1672
+ const parsed = JSON.parse(readFileSync2(eventPath, "utf-8"));
1673
+ return isRecord(parsed) ? parsed : {};
1674
+ } catch {
1675
+ return {};
1676
+ }
1677
+ }
1678
+ function isRecord(value) {
1679
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1680
+ }
1681
+ function pick(obj, ...keys) {
1682
+ let current = obj;
1683
+ for (const key of keys) {
1684
+ if (!isRecord(current)) return void 0;
1685
+ current = current[key];
1686
+ }
1687
+ return typeof current === "string" ? current : void 0;
1688
+ }
1689
+ async function resolveBuildInfo(options) {
1690
+ const { cwd, env = process.env, git = runGit, includeRepo = false } = options;
1691
+ const event = readEvent(env.GITHUB_EVENT_PATH);
1692
+ const isPullRequest = env.GITHUB_EVENT_NAME === "pull_request" || env.GITHUB_EVENT_NAME === "pull_request_target";
1693
+ const netlify = env.NETLIFY === "true";
1694
+ const gitlab = env.GITLAB_CI === "true";
1695
+ const branch = await firstValid(
1696
+ [
1697
+ env.GITHUB_HEAD_REF,
1698
+ env.GITHUB_REF_TYPE === "branch" ? env.GITHUB_REF_NAME : null,
1699
+ env.VERCEL_GIT_COMMIT_REF,
1700
+ env.CF_PAGES_BRANCH,
1701
+ netlify ? env.BRANCH : null,
1702
+ gitlab ? env.CI_COMMIT_REF_NAME : null,
1703
+ async () => {
1704
+ const head = await git(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
1705
+ return head === "HEAD" ? null : head;
1706
+ }
1707
+ ],
1708
+ asBranch
1709
+ );
1710
+ const base = await firstValid(
1711
+ [
1712
+ isPullRequest ? pick(event, "pull_request", "head", "sha") : null,
1713
+ env.GITHUB_SHA,
1714
+ env.VERCEL_GIT_COMMIT_SHA,
1715
+ env.CF_PAGES_COMMIT_SHA,
1716
+ netlify ? env.COMMIT_REF : null,
1717
+ gitlab ? env.CI_COMMIT_SHA : null,
1718
+ () => git(["rev-parse", "HEAD"], cwd)
1719
+ ],
1720
+ asSha
1721
+ );
1722
+ const defaultBranch = await firstValid(
1723
+ [
1724
+ pick(event, "repository", "default_branch"),
1725
+ async () => {
1726
+ const ref = await git(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], cwd);
1727
+ return ref?.startsWith("origin/") ? ref.slice("origin/".length) : null;
1728
+ }
1729
+ ],
1730
+ asBranch
1731
+ );
1732
+ const githubCom = env.GITHUB_SERVER_URL === void 0 || env.GITHUB_SERVER_URL === "" || env.GITHUB_SERVER_URL === "https://github.com";
1733
+ const repo = includeRepo ? await firstValid(
1734
+ [
1735
+ githubCom ? env.GITHUB_REPOSITORY : null,
1736
+ env.VERCEL_GIT_PROVIDER === "github" && env.VERCEL_GIT_REPO_OWNER && env.VERCEL_GIT_REPO_SLUG ? `${env.VERCEL_GIT_REPO_OWNER}/${env.VERCEL_GIT_REPO_SLUG}` : null,
1737
+ netlify && env.REPOSITORY_URL ? parseGitHubSlug(env.REPOSITORY_URL) : null,
1738
+ async () => {
1739
+ const remote = await git(["remote", "get-url", "origin"], cwd);
1740
+ return remote ? parseGitHubSlug(remote) : null;
1741
+ }
1742
+ ],
1743
+ asSlug
1744
+ ) : null;
1745
+ const top = await git(["rev-parse", "--show-toplevel"], cwd);
1746
+ const gitRoot = top ? path5.resolve(top) : null;
1747
+ return { repo, defaultBranch, branch, base, gitRoot };
1748
+ }
1749
+ function parseGitHubSlug(remote) {
1750
+ const text = remote.trim();
1751
+ let repoPath;
1752
+ const scp = /^[A-Za-z0-9._-]+@github\.com:(.*)$/i.exec(text);
1753
+ if (scp) {
1754
+ repoPath = scp[1] ?? "";
1755
+ } else {
1756
+ let url;
1757
+ try {
1758
+ url = new URL(text);
1759
+ } catch {
1760
+ return null;
1761
+ }
1762
+ if (!["https:", "http:", "ssh:", "git:"].includes(url.protocol)) return null;
1763
+ if (url.hostname.toLowerCase() !== "github.com") return null;
1764
+ repoPath = url.pathname;
1765
+ }
1766
+ const trimmed = repoPath.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.git$/i, "");
1767
+ return isValidRepoSlug(trimmed) ? trimmed : null;
1768
+ }
1769
+
1770
+ // src/server/build-target.ts
1771
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
1772
+ import * as path6 from "path";
1773
+ var NEXT_CONFIG_FILES = [
1774
+ "next.config.js",
1775
+ "next.config.mjs",
1776
+ "next.config.cjs",
1777
+ "next.config.ts",
1778
+ "next.config.mts"
1779
+ ];
1780
+ var VITE_CONFIG_FILES = [
1781
+ "vite.config.js",
1782
+ "vite.config.mjs",
1783
+ "vite.config.cjs",
1784
+ "vite.config.ts",
1785
+ "vite.config.mts"
1786
+ ];
1787
+ var OUTPUT_EXPORT = /\boutput\s*:\s*['"`]export['"`]/;
1788
+ var BASE_PATH = /\bbasePath\s*:\s*['"`](\/[^'"`]*)['"`]/;
1789
+ var VITE_BASE = /\bbase\s*:\s*['"`](\/[^'"`]*|\.\/?|)['"`]/;
1790
+ function readText(file) {
1791
+ try {
1792
+ return readFileSync3(file, "utf-8");
1793
+ } catch {
1794
+ return null;
1795
+ }
1796
+ }
1797
+ function readPackageJson(projectDir) {
1798
+ const text = readText(path6.join(projectDir, "package.json"));
1799
+ if (text === null) return {};
1800
+ try {
1801
+ const parsed = JSON.parse(text);
1802
+ return parsed && typeof parsed === "object" ? parsed : {};
1803
+ } catch {
1804
+ return {};
1805
+ }
1806
+ }
1807
+ function hasDependency(pkg, name) {
1808
+ return Boolean(pkg.dependencies?.[name] ?? pkg.devDependencies?.[name]);
1809
+ }
1810
+ function firstExisting(projectDir, names) {
1811
+ for (const name of names) {
1812
+ const file = path6.join(projectDir, name);
1813
+ if (existsSync2(file)) return file;
1814
+ }
1815
+ return null;
1816
+ }
1817
+ function stripComments(source) {
1818
+ return source.replace(/\/\*[\s\S]*?\*\//g, "").split("\n").filter((line) => !line.trimStart().startsWith("//")).join("\n");
1819
+ }
1820
+ function craBase(homepage) {
1821
+ if (typeof homepage !== "string") return null;
1822
+ const value = homepage.trim();
1823
+ if (value === "." || value === "./") return "./";
1824
+ if (value.startsWith("/")) return value;
1825
+ try {
1826
+ return new URL(value).pathname;
1827
+ } catch {
1828
+ return null;
1829
+ }
1830
+ }
1831
+ function detectBuildTarget(projectDir) {
1832
+ const pkg = readPackageJson(projectDir);
1833
+ const nextConfig = firstExisting(projectDir, NEXT_CONFIG_FILES);
1834
+ if (nextConfig || hasDependency(pkg, "next")) {
1835
+ const source = stripComments((nextConfig && readText(nextConfig)) ?? "");
1836
+ return {
1837
+ kind: "next",
1838
+ staticExport: OUTPUT_EXPORT.test(source),
1839
+ base: BASE_PATH.exec(source)?.[1] ?? null
1840
+ };
1841
+ }
1842
+ if (hasDependency(pkg, "react-scripts")) return { kind: "cra", base: craBase(pkg.homepage) };
1843
+ if (hasDependency(pkg, "vite") || firstExisting(projectDir, VITE_CONFIG_FILES)) {
1844
+ const viteConfig = firstExisting(projectDir, VITE_CONFIG_FILES);
1845
+ const source = stripComments((viteConfig && readText(viteConfig)) ?? "");
1846
+ return { kind: "vite", base: VITE_BASE.exec(source)?.[1] ?? null };
1847
+ }
1848
+ return { kind: "other", base: null };
1849
+ }
1850
+
1851
+ // src/server/module-dir.ts
1852
+ import * as path7 from "path";
1853
+ import { fileURLToPath } from "url";
1854
+ function moduleDir() {
1855
+ if (typeof __dirname !== "undefined") {
1856
+ return __dirname;
1857
+ }
1858
+ return path7.dirname(fileURLToPath(import.meta.url));
1859
+ }
1860
+
1861
+ // src/server/pack-error.ts
1862
+ var PackError = class extends Error {
1863
+ code;
1864
+ constructor(code, message) {
1865
+ super(message);
1866
+ this.name = "PackError";
1867
+ this.code = code;
1868
+ }
1869
+ };
1870
+
1871
+ // src/server/share-html.ts
1872
+ var FULL_URL = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
1873
+ var WINDOWS_PATH = /^[A-Za-z]:[\\/]/;
1874
+ var SEGMENT = /^(?:[A-Za-z0-9._~@+-]|%[0-9A-Fa-f]{2})+$/;
1875
+ function normalizeBase(value) {
1876
+ if (value === "" || value === "." || value === "./") return { kind: "relative" };
1877
+ if (WINDOWS_PATH.test(value)) {
1878
+ throw new PackError(
1879
+ "BAD_BASE",
1880
+ `The app base "${value}" is a Windows path. Your shell (Git Bash/MSYS) probably converted a site path such as /repo/ into it. Run the command with MSYS_NO_PATHCONV=1, or from an npm script, and pass the site path, for example --base /app/.`
1881
+ );
1882
+ }
1883
+ if (FULL_URL.test(value)) {
1884
+ throw new PackError(
1885
+ "BAD_BASE",
1886
+ `The app base "${value}" is a full URL. Pass the path your HTML is served from, for example --base /app/ (shareBase in the Vite plugin).`
1887
+ );
1888
+ }
1889
+ const notASitePath = () => new PackError(
1890
+ "BAD_BASE",
1891
+ `The app base "${value}" is not a site path. Use "/", "/app/" or "./".`
1892
+ );
1893
+ if (!value.startsWith("/")) throw notASitePath();
1894
+ const segments = value.slice(1).split("/");
1895
+ if (segments[segments.length - 1] === "") segments.pop();
1896
+ for (const segment of segments) {
1897
+ if (segment === "." || segment === ".." || !SEGMENT.test(segment)) throw notASitePath();
1898
+ }
1899
+ return { kind: "absolute", path: segments.length === 0 ? "/" : `/${segments.join("/")}/` };
1900
+ }
1901
+ function basePathOf(base) {
1902
+ return base.kind === "absolute" ? base.path : null;
1903
+ }
1904
+ function flagSrcFor(base, htmlRelPath) {
1905
+ const file = `${SHARE_DIR}/${SHARE_FLAG_FILE}`;
1906
+ if (base.kind === "absolute") return `${base.path}${file}`;
1907
+ const depth = htmlRelPath.split("/").length - 1;
1908
+ return depth === 0 ? `./${file}` : `${"../".repeat(depth)}${file}`;
1909
+ }
1910
+ function shareUrlPathFor(base) {
1911
+ const page = `${SHARE_DIR}/index.html`;
1912
+ return base.kind === "absolute" ? `${base.path}${page}` : page;
1913
+ }
1914
+ var EXISTING_FLAG_TAG = /<script\b[^>]*\bsrc="[^"]*livelocale\/flag\.js"[^>]*>\s*<\/script>/gi;
1915
+ var HEAD_OPEN = /<head(?=[\s>])[^>]*>/i;
1916
+ var HTML_OPEN = /<html(?=[\s>])[^>]*>/i;
1917
+ var DOCTYPE = /<!doctype\b[^>]*>/i;
1918
+ function escapeAttribute(value) {
1919
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1920
+ }
1921
+ function injectFlagScript(html, src) {
1922
+ const tag = `<script src="${escapeAttribute(src)}"></script>`;
1923
+ const cleaned = html.replace(EXISTING_FLAG_TAG, "");
1924
+ for (const pattern of [HEAD_OPEN, HTML_OPEN, DOCTYPE]) {
1925
+ const match = pattern.exec(cleaned);
1926
+ if (match) {
1927
+ const end = match.index + match[0].length;
1928
+ return cleaned.slice(0, end) + tag + cleaned.slice(end);
1929
+ }
1930
+ }
1931
+ return tag + cleaned;
1932
+ }
1933
+
1934
+ // src/server/pack.ts
1935
+ var NO_REPO_WARNING = "exposeRepo is on but no GitHub repository was found; the manifest has no repo.";
1936
+ function isPlainObject(value) {
1937
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1938
+ }
1939
+ function toPosix(relative4) {
1940
+ return relative4.split(path8.sep).join("/") || ".";
1941
+ }
1942
+ function isInside(parent, child) {
1943
+ const rel = path8.relative(parent, child);
1944
+ return rel === "" || !rel.startsWith("..") && !path8.isAbsolute(rel);
1945
+ }
1946
+ function defineOwn(target, key, value) {
1947
+ Object.defineProperty(target, key, {
1948
+ value,
1949
+ enumerable: true,
1950
+ writable: true,
1951
+ configurable: true
1952
+ });
1953
+ }
1954
+ async function readJsonObject(file, projectDir) {
1955
+ const shown = toPosix(path8.relative(projectDir, file));
1956
+ let parsed;
1957
+ try {
1958
+ parsed = JSON.parse(await fs4.readFile(file, "utf-8"));
1959
+ } catch (error) {
1960
+ throw new PackError(
1961
+ "INVALID_LOCALE_FILE",
1962
+ `${shown} is not a JSON object: ${error instanceof Error ? error.message : String(error)}`
1963
+ );
1964
+ }
1965
+ if (!isPlainObject(parsed)) {
1966
+ const kind = Array.isArray(parsed) ? "an array" : parsed === null ? "null" : typeof parsed;
1967
+ throw new PackError(
1968
+ "INVALID_LOCALE_FILE",
1969
+ `${shown} is not a JSON object: the root is ${kind}`
1970
+ );
1971
+ }
1972
+ return parsed;
1973
+ }
1974
+ async function collectShareData(options) {
1975
+ const projectDir = path8.resolve(options.projectDir);
1976
+ const localesDir = path8.resolve(projectDir, options.localesPath ?? "locales");
1977
+ const warnings = [];
1978
+ let entries;
1979
+ try {
1980
+ if (!(await fs4.stat(localesDir)).isDirectory()) throw new Error("not a directory");
1981
+ entries = await fs4.readdir(localesDir, { withFileTypes: true });
1982
+ } catch {
1983
+ throw new PackError(
1984
+ "LOCALES_NOT_FOUND",
1985
+ `Locales directory not found: ${localesDir}. Pass --locales <path>.`
1986
+ );
1987
+ }
1988
+ const dirLocales = /* @__PURE__ */ new Set();
1989
+ const fileLocales = /* @__PURE__ */ new Set();
1990
+ for (const entry of entries) {
1991
+ if (entry.isDirectory() && LOCALE_RE.test(entry.name)) {
1992
+ dirLocales.add(entry.name);
1993
+ } else if (entry.isFile() && entry.name.endsWith(".json")) {
1994
+ const code = entry.name.slice(0, -".json".length);
1995
+ if (LOCALE_RE.test(code)) fileLocales.add(code);
1996
+ }
1997
+ }
1998
+ const locales = [.../* @__PURE__ */ new Set([...dirLocales, ...fileLocales])].sort();
1999
+ if (locales.length === 0) {
2000
+ throw new PackError(
2001
+ "NO_LOCALES",
2002
+ `No locale files in ${localesDir} (expected en.json or en/*.json).`
2003
+ );
2004
+ }
2005
+ const layout = dirLocales.size > 0 ? "namespaced" : "flat";
2006
+ const sourceLocale = options.sourceLocale ?? (locales.includes("en") ? "en" : locales[0]);
2007
+ if (!locales.includes(sourceLocale)) {
2008
+ throw new PackError(
2009
+ "SOURCE_LOCALE_NOT_FOUND",
2010
+ `Source locale "${sourceLocale}" not found in ${localesDir} (found: ${locales.join(", ")}).`
2011
+ );
2012
+ }
2013
+ const dictionaries = {};
2014
+ for (const locale of locales) {
2015
+ let dictionary;
2016
+ if (dirLocales.has(locale)) {
2017
+ dictionary = {};
2018
+ const folder = path8.join(localesDir, locale);
2019
+ const files = (await fs4.readdir(folder, { withFileTypes: true })).filter((file) => file.isFile() && file.name.endsWith(".json")).map((file) => file.name).sort();
2020
+ for (const file of files) {
2021
+ const namespace = file.slice(0, -".json".length);
2022
+ defineOwn(dictionary, namespace, await readJsonObject(path8.join(folder, file), projectDir));
2023
+ }
2024
+ } else {
2025
+ dictionary = await readJsonObject(path8.join(localesDir, `${locale}.json`), projectDir);
2026
+ }
2027
+ defineOwn(dictionaries, locale, dictionary);
2028
+ }
2029
+ const info = await resolveBuildInfo({
2030
+ cwd: projectDir,
2031
+ env: options.env,
2032
+ git: options.git,
2033
+ includeRepo: options.exposeRepo === true
2034
+ });
2035
+ const root = info.gitRoot !== null && isInside(info.gitRoot, localesDir) ? info.gitRoot : projectDir;
2036
+ const codeKeys = await new KeyScanner({ requireEnabled: false }).scanSourceKeys(projectDir);
2037
+ if (options.exposeRepo === true && info.repo === null) warnings.push(NO_REPO_WARNING);
2038
+ const manifest = {
2039
+ format: SHARE_MANIFEST_FORMAT,
2040
+ ...options.exposeRepo === true && info.repo !== null ? { repo: info.repo } : {},
2041
+ defaultBranch: info.defaultBranch,
2042
+ branch: info.branch,
2043
+ base: info.base,
2044
+ localesPath: toPosix(path8.relative(root, localesDir)),
2045
+ layout,
2046
+ sourceLocale,
2047
+ locales,
2048
+ basePath: options.basePath,
2049
+ codeKeys: [...codeKeys].sort()
2050
+ };
2051
+ return { manifest, dictionaries, warnings };
2052
+ }
2053
+ var RELATIVE_BASE_WARNING = "The app base is relative (./), so flag.js is linked per HTML file. Pages served by a client-side route fallback cannot load it; build with an absolute base (--base /path/) if the app has such routes.";
2054
+ function defaultStudioCandidates() {
2055
+ const here = moduleDir();
2056
+ return [path8.resolve(here, "../studio"), path8.resolve(here, "../../dist/studio")];
2057
+ }
2058
+ function isDirectory(target) {
2059
+ try {
2060
+ return statSync(target).isDirectory();
2061
+ } catch {
2062
+ return false;
2063
+ }
2064
+ }
2065
+ function resolveStudioDir(candidates = defaultStudioCandidates()) {
2066
+ for (const dir of candidates) {
2067
+ if (existsSync3(path8.join(dir, "index.html")) && isDirectory(path8.join(dir, "assets"))) {
2068
+ return dir;
2069
+ }
2070
+ }
2071
+ return null;
2072
+ }
2073
+ async function ownsShareDir(shareDir) {
2074
+ try {
2075
+ await fs4.lstat(shareDir);
2076
+ } catch {
2077
+ return true;
2078
+ }
2079
+ try {
2080
+ const manifest = JSON.parse(
2081
+ await fs4.readFile(path8.join(shareDir, SHARE_MANIFEST_FILE), "utf-8")
2082
+ );
2083
+ return isPlainObject(manifest) && typeof manifest["format"] === "number";
2084
+ } catch {
2085
+ return false;
2086
+ }
2087
+ }
2088
+ async function findHtmlFiles(outDir, shareDir) {
2089
+ const found = [];
2090
+ const walk = async (dir) => {
2091
+ for (const entry of await fs4.readdir(dir, { withFileTypes: true })) {
2092
+ if (entry.isSymbolicLink()) continue;
2093
+ const full = path8.join(dir, entry.name);
2094
+ if (entry.isDirectory()) {
2095
+ if (full !== shareDir) await walk(full);
2096
+ } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".html")) {
2097
+ found.push(toPosix(path8.relative(outDir, full)));
2098
+ }
2099
+ }
2100
+ };
2101
+ await walk(outDir);
2102
+ return found.sort();
2103
+ }
2104
+ async function packOutput(options) {
2105
+ const {
2106
+ outDir: outOption,
2107
+ base: baseOption,
2108
+ injectHtml = "auto",
2109
+ studioDir: studioOption,
2110
+ ...shareOptions
2111
+ } = options;
2112
+ const projectDir = path8.resolve(options.projectDir);
2113
+ const outDir = path8.resolve(projectDir, outOption);
2114
+ if (!isDirectory(outDir)) {
2115
+ throw new PackError(
2116
+ "OUT_DIR_NOT_FOUND",
2117
+ `Build output not found: ${outDir}. Run your build first.`
2118
+ );
2119
+ }
2120
+ const target = detectBuildTarget(projectDir);
2121
+ const base = normalizeBase(baseOption ?? target.base ?? "/");
2122
+ const candidates = studioOption !== void 0 ? [path8.resolve(projectDir, studioOption)] : defaultStudioCandidates();
2123
+ const studioDir = resolveStudioDir(candidates);
2124
+ if (studioDir === null) {
2125
+ throw new PackError(
2126
+ "STUDIO_NOT_FOUND",
2127
+ `LiveLocale Studio files are missing from the installed package (looked in ${candidates.join(", ")}). Reinstall livelocale.`
2128
+ );
2129
+ }
2130
+ const data = await collectShareData({ ...shareOptions, projectDir, basePath: basePathOf(base) });
2131
+ const shareDir = path8.join(outDir, SHARE_DIR);
2132
+ if (!await ownsShareDir(shareDir)) {
2133
+ throw new PackError(
2134
+ "SHARE_DIR_TAKEN",
2135
+ `${toPosix(path8.relative(projectDir, outDir))}/${SHARE_DIR}/ already exists and was not written by LiveLocale. Move it away and run again.`
2136
+ );
2137
+ }
2138
+ await fs4.rm(shareDir, { recursive: true, force: true });
2139
+ await fs4.cp(studioDir, shareDir, { recursive: true });
2140
+ await fs4.writeFile(
2141
+ path8.join(shareDir, SHARE_MANIFEST_FILE),
2142
+ JSON.stringify(data.manifest, null, 2) + "\n",
2143
+ "utf-8"
2144
+ );
2145
+ const localesDir = path8.join(shareDir, SHARE_LOCALES_DIR);
2146
+ await fs4.mkdir(localesDir, { recursive: true });
2147
+ for (const locale of data.manifest.locales) {
2148
+ await fs4.writeFile(
2149
+ path8.join(localesDir, `${locale}.json`),
2150
+ JSON.stringify(data.dictionaries[locale]),
2151
+ "utf-8"
2152
+ );
2153
+ }
2154
+ await fs4.writeFile(path8.join(shareDir, SHARE_FLAG_FILE), FLAG_SCRIPT, "utf-8");
2155
+ const inject = injectHtml === "auto" ? target.kind !== "next" && !existsSync3(path8.join(outDir, "_next")) : injectHtml;
2156
+ const htmlInjected = inject ? await findHtmlFiles(outDir, shareDir) : [];
2157
+ for (const rel of htmlInjected) {
2158
+ const file = path8.join(outDir, ...rel.split("/"));
2159
+ const html = await fs4.readFile(file, "utf-8");
2160
+ const injected = injectFlagScript(html, flagSrcFor(base, rel));
2161
+ if (injected !== html) await fs4.writeFile(file, injected, "utf-8");
2162
+ }
2163
+ const warnings = [...data.warnings];
2164
+ if (base.kind === "relative") warnings.push(RELATIVE_BASE_WARNING);
2165
+ return {
2166
+ shareDir,
2167
+ manifest: data.manifest,
2168
+ htmlInjected,
2169
+ shareUrlPath: shareUrlPathFor(base),
2170
+ warnings
2171
+ };
2172
+ }
2173
+
1554
2174
  // src/server/vite-plugin.ts
1555
2175
  var ROUTE = "/api/livelocale";
1556
2176
  function liveLocaleVite(config) {
@@ -1560,10 +2180,47 @@ function liveLocaleVite(config) {
1560
2180
  git: config.git === false ? void 0 : new GitClient(),
1561
2181
  authToken: config.authToken ?? process.env["LIVELOCALE_TOKEN"]
1562
2182
  });
2183
+ let shareBuild = null;
2184
+ let packed = false;
1563
2185
  return {
1564
2186
  name: "livelocale",
1565
- // Only active during dev server mode; the plugin is a no-op during builds.
1566
- apply: "serve",
2187
+ // Always active in dev; in builds only with `share: true`, and never for the SSR build.
2188
+ apply(_userConfig, env) {
2189
+ if (env.command === "serve") return true;
2190
+ return config.share === true && !env.isSsrBuild && !env.ssrBuild;
2191
+ },
2192
+ configResolved(resolved) {
2193
+ shareBuild = null;
2194
+ if (resolved.command !== "build" || config.share !== true) return;
2195
+ if (resolved.build?.ssr || resolved.build?.lib) return;
2196
+ const base = config.shareBase ?? resolved.base;
2197
+ shareBuild = {
2198
+ root: resolved.root,
2199
+ outDir: path9.resolve(resolved.root, resolved.build.outDir),
2200
+ base,
2201
+ appBase: normalizeBase(base),
2202
+ logger: resolved.logger
2203
+ };
2204
+ },
2205
+ buildStart() {
2206
+ packed = false;
2207
+ },
2208
+ async writeBundle() {
2209
+ if (shareBuild === null || packed) return;
2210
+ packed = true;
2211
+ const result = await packOutput({
2212
+ outDir: shareBuild.outDir,
2213
+ projectDir: shareBuild.root,
2214
+ // Resolved against the working directory, as the serve half does.
2215
+ localesPath: path9.resolve(config.localesPath),
2216
+ sourceLocale: config.sourceLocale,
2217
+ exposeRepo: config.exposeRepo ?? false,
2218
+ base: shareBuild.base,
2219
+ // transformIndexHtml already linked flag.js in every HTML file Vite wrote.
2220
+ injectHtml: false
2221
+ });
2222
+ for (const warning of result.warnings) shareBuild.logger.warn(warning);
2223
+ },
1567
2224
  configureServer(server) {
1568
2225
  server.middlewares.use(async (req, res, next) => {
1569
2226
  const url = req.url ?? "";
@@ -1600,7 +2257,11 @@ function liveLocaleVite(config) {
1600
2257
  }
1601
2258
  });
1602
2259
  },
1603
- transformIndexHtml(html) {
2260
+ transformIndexHtml(html, ctx) {
2261
+ if (shareBuild !== null) {
2262
+ const htmlRelPath = (ctx?.path ?? "/index.html").replace(/^\/+/, "");
2263
+ return injectFlagScript(html, flagSrcFor(shareBuild.appBase, htmlRelPath));
2264
+ }
1604
2265
  const flag = process.env["LIVELOCALE"];
1605
2266
  const flagScript = flag ? `<script>window.${BROWSER_FLAG_GLOBAL} = ${JSON.stringify(flag)};</script>
1606
2267
  ` : "";
@@ -1608,24 +2269,24 @@ function liveLocaleVite(config) {
1608
2269
  return flagScript ? html.replace("</head>", `${flagScript}</head>`) : html;
1609
2270
  }
1610
2271
  try {
1611
- const resolvedLocalesPath = path5.resolve(config.localesPath);
1612
- const entries = fs4.readdirSync(resolvedLocalesPath);
2272
+ const resolvedLocalesPath = path9.resolve(config.localesPath);
2273
+ const entries = fs5.readdirSync(resolvedLocalesPath);
1613
2274
  let fallbackData = {};
1614
2275
  for (const entry of entries) {
1615
2276
  if (entry.startsWith(".")) continue;
1616
- const entryPath = path5.join(resolvedLocalesPath, entry);
2277
+ const entryPath = path9.join(resolvedLocalesPath, entry);
1617
2278
  let entryIsDir = false;
1618
2279
  try {
1619
- entryIsDir = fs4.statSync(entryPath).isDirectory();
2280
+ entryIsDir = fs5.statSync(entryPath).isDirectory();
1620
2281
  } catch {
1621
2282
  }
1622
2283
  if (entryIsDir) {
1623
2284
  try {
1624
- const files = fs4.readdirSync(entryPath);
2285
+ const files = fs5.readdirSync(entryPath);
1625
2286
  for (const file of files) {
1626
2287
  if (file.endsWith(".json")) {
1627
2288
  const ns = file.slice(0, -5);
1628
- const raw = fs4.readFileSync(path5.join(entryPath, file), "utf-8");
2289
+ const raw = fs5.readFileSync(path9.join(entryPath, file), "utf-8");
1629
2290
  try {
1630
2291
  const parsed = JSON.parse(raw);
1631
2292
  fallbackData[ns] = deepMerge(fallbackData[ns] || {}, parsed);
@@ -1636,7 +2297,7 @@ function liveLocaleVite(config) {
1636
2297
  } catch {
1637
2298
  }
1638
2299
  } else if (entry.endsWith(".json")) {
1639
- const raw = fs4.readFileSync(entryPath, "utf-8");
2300
+ const raw = fs5.readFileSync(entryPath, "utf-8");
1640
2301
  try {
1641
2302
  const parsed = JSON.parse(raw);
1642
2303
  fallbackData = deepMerge(fallbackData, parsed);