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,8 +1,18 @@
1
- export { W as WrapOptions, d as decodeKey, e as encodeKey, h as hasWatermark, s as sanitizeZeroWidth, a as stripWatermark, u as unwrap, w as wrapNextIntl, b as wrapTranslationEngine } from '../wrap-CsOtHp8r.cjs';
1
+ export { W as WrapOptions, d as decodeKey, e as encodeKey, h as hasWatermark, r as removeWatermarks, s as sanitizeZeroWidth, a as stripWatermark, u as unwrap, w as wrapNextIntl, b as wrapTranslationEngine } from '../wrap-2qVxLN9Q.cjs';
2
2
 
3
+ interface FileMutatorOptions {
4
+ /** Refuse to run unless LiveLocale is enabled. Default true; only CLI tooling passes false. */
5
+ requireEnabled?: boolean;
6
+ /** Skip the console line each `updateLocaleKey` write prints. Default false. */
7
+ quiet?: boolean;
8
+ }
3
9
  declare class FileMutator {
4
10
  private fileLocks;
5
- private scanner;
11
+ private readonly requireEnabled;
12
+ private readonly quiet;
13
+ private readonly scanner;
14
+ constructor(options?: FileMutatorOptions);
15
+ private assertEnabled;
6
16
  /**
7
17
  * Helper to serialize execution per file path
8
18
  */
@@ -359,4 +369,4 @@ declare function getLiveLocaleMode(): LiveLocaleMode;
359
369
  /** True when watermarking, the mutation API, and the client scanner should run. */
360
370
  declare function isLiveLocaleEnabled(): boolean;
361
371
 
362
- export { type CreateTranslationsOptions, FileMutator, type GitChange, GitClient, type GitGateway, type GitHubGateway, type GitHubGatewayConfig, type GitStatus, type HandlerConfig, KeyScanner, type LiveLocaleMode, type MutationGateway, type MutationRouter, createGitHubGateway, createLiveLocaleHandler, createMutationRouter, createTranslations, getLiveLocaleMode, isLiveLocaleEnabled };
372
+ export { type CreateTranslationsOptions, FileMutator, type FileMutatorOptions, type GitChange, GitClient, type GitGateway, type GitHubGateway, type GitHubGatewayConfig, type GitStatus, type HandlerConfig, KeyScanner, type LiveLocaleMode, type MutationGateway, type MutationRouter, createGitHubGateway, createLiveLocaleHandler, createMutationRouter, createTranslations, getLiveLocaleMode, isLiveLocaleEnabled };
@@ -1,8 +1,18 @@
1
- export { W as WrapOptions, d as decodeKey, e as encodeKey, h as hasWatermark, s as sanitizeZeroWidth, a as stripWatermark, u as unwrap, w as wrapNextIntl, b as wrapTranslationEngine } from '../wrap-CsOtHp8r.js';
1
+ export { W as WrapOptions, d as decodeKey, e as encodeKey, h as hasWatermark, r as removeWatermarks, s as sanitizeZeroWidth, a as stripWatermark, u as unwrap, w as wrapNextIntl, b as wrapTranslationEngine } from '../wrap-2qVxLN9Q.js';
2
2
 
3
+ interface FileMutatorOptions {
4
+ /** Refuse to run unless LiveLocale is enabled. Default true; only CLI tooling passes false. */
5
+ requireEnabled?: boolean;
6
+ /** Skip the console line each `updateLocaleKey` write prints. Default false. */
7
+ quiet?: boolean;
8
+ }
3
9
  declare class FileMutator {
4
10
  private fileLocks;
5
- private scanner;
11
+ private readonly requireEnabled;
12
+ private readonly quiet;
13
+ private readonly scanner;
14
+ constructor(options?: FileMutatorOptions);
15
+ private assertEnabled;
6
16
  /**
7
17
  * Helper to serialize execution per file path
8
18
  */
@@ -359,4 +369,4 @@ declare function getLiveLocaleMode(): LiveLocaleMode;
359
369
  /** True when watermarking, the mutation API, and the client scanner should run. */
360
370
  declare function isLiveLocaleEnabled(): boolean;
361
371
 
362
- export { type CreateTranslationsOptions, FileMutator, type GitChange, GitClient, type GitGateway, type GitHubGateway, type GitHubGatewayConfig, type GitStatus, type HandlerConfig, KeyScanner, type LiveLocaleMode, type MutationGateway, type MutationRouter, createGitHubGateway, createLiveLocaleHandler, createMutationRouter, createTranslations, getLiveLocaleMode, isLiveLocaleEnabled };
372
+ export { type CreateTranslationsOptions, FileMutator, type FileMutatorOptions, type GitChange, GitClient, type GitGateway, type GitHubGateway, type GitHubGatewayConfig, type GitStatus, type HandlerConfig, KeyScanner, type LiveLocaleMode, type MutationGateway, type MutationRouter, createGitHubGateway, createLiveLocaleHandler, createMutationRouter, createTranslations, getLiveLocaleMode, isLiveLocaleEnabled };
@@ -540,14 +540,6 @@ var KeyScanner = class {
540
540
  };
541
541
 
542
542
  // src/server/file-mutator.ts
543
- function assertEnabled(component) {
544
- const reason = getDisabledReason();
545
- if (reason !== null) {
546
- throw new Error(
547
- `[livelocale] ${component} can only be executed in development mode (${reason}).`
548
- );
549
- }
550
- }
551
543
  var FileMutex = class {
552
544
  queue = Promise.resolve();
553
545
  async acquire(fn) {
@@ -563,7 +555,23 @@ var FileMutex = class {
563
555
  };
564
556
  var FileMutator = class {
565
557
  fileLocks = /* @__PURE__ */ new Map();
566
- scanner = new KeyScanner();
558
+ requireEnabled;
559
+ quiet;
560
+ scanner;
561
+ constructor(options = {}) {
562
+ this.requireEnabled = options.requireEnabled ?? true;
563
+ this.quiet = options.quiet ?? false;
564
+ this.scanner = new KeyScanner({ requireEnabled: this.requireEnabled });
565
+ }
566
+ assertEnabled() {
567
+ if (!this.requireEnabled) return;
568
+ const reason = getDisabledReason();
569
+ if (reason !== null) {
570
+ throw new Error(
571
+ `[livelocale] FileMutator can only be executed in development mode (${reason}).`
572
+ );
573
+ }
574
+ }
567
575
  /**
568
576
  * Helper to serialize execution per file path
569
577
  */
@@ -581,7 +589,7 @@ var FileMutator = class {
581
589
  * structural JSON validation, and concurrency serialization.
582
590
  */
583
591
  async updateLocaleKey(basePath, locale, keyPath, newValue) {
584
- assertEnabled("FileMutator");
592
+ this.assertEnabled();
585
593
  if (!isSafeLocaleSegment(locale)) {
586
594
  throw new Error(
587
595
  `[livelocale] Path traversal attempt detected in locale identifier: "${locale}"`
@@ -684,7 +692,9 @@ var FileMutator = class {
684
692
  `[livelocale] Failed to rename temp file to target at ${filePath}. Operation: rename. Original error: ${err.message}`
685
693
  );
686
694
  }
687
- console.log(`[livelocale] Updated key '${keyPath}' in locale '${locale}'`);
695
+ if (!this.quiet) {
696
+ console.log(`[livelocale] Updated key '${keyPath}' in locale '${locale}'`);
697
+ }
688
698
  });
689
699
  }
690
700
  /**
@@ -692,7 +702,7 @@ var FileMutator = class {
692
702
  * `*.json` inside the `{locale}/` folder merged under its namespace.
693
703
  */
694
704
  async readLocale(basePath, locale) {
695
- assertEnabled("FileMutator");
705
+ this.assertEnabled();
696
706
  const localePath = path3.resolve(basePath, locale);
697
707
  let isDir = false;
698
708
  try {
@@ -719,7 +729,7 @@ var FileMutator = class {
719
729
  * Lists all locale codes dynamically scanned from the locales directory.
720
730
  */
721
731
  async listLocales(basePath) {
722
- assertEnabled("FileMutator");
732
+ this.assertEnabled();
723
733
  const resolvedBasePath = path3.resolve(basePath);
724
734
  const items = await fs2.readdir(resolvedBasePath, { withFileTypes: true });
725
735
  const localesSet = /* @__PURE__ */ new Set();
@@ -741,7 +751,7 @@ var FileMutator = class {
741
751
  * Adds a new locale directory/file based on current repository structure.
742
752
  */
743
753
  async addLocale(basePath, locale) {
744
- assertEnabled("FileMutator");
754
+ this.assertEnabled();
745
755
  if (!isSafeLocaleSegment(locale)) {
746
756
  throw new Error(
747
757
  `[livelocale] Path traversal attempt detected in locale identifier: "${locale}"`
@@ -797,7 +807,7 @@ var FileMutator = class {
797
807
  * Renames an existing locale (directory or file) to a new name.
798
808
  */
799
809
  async renameLocale(basePath, oldLocale, newLocale) {
800
- assertEnabled("FileMutator");
810
+ this.assertEnabled();
801
811
  if (!isSafeLocaleSegment(oldLocale) || !isSafeLocaleSegment(newLocale)) {
802
812
  throw new Error("[livelocale] Path traversal attempt detected");
803
813
  }
@@ -855,7 +865,7 @@ var FileMutator = class {
855
865
  * Deletes an existing locale (directory or file).
856
866
  */
857
867
  async deleteLocale(basePath, locale) {
858
- assertEnabled("FileMutator");
868
+ this.assertEnabled();
859
869
  if (!isSafeLocaleSegment(locale)) {
860
870
  throw new Error("[livelocale] Path traversal attempt detected");
861
871
  }
@@ -1646,6 +1656,67 @@ function validateMessageSyntax(message) {
1646
1656
  return parseIcu(prepared).error;
1647
1657
  }
1648
1658
 
1659
+ // src/core/save-rules.ts
1660
+ var reject = (status, code, error) => ({
1661
+ ok: false,
1662
+ status,
1663
+ code,
1664
+ error
1665
+ });
1666
+ function validateWrite(input, options = {}) {
1667
+ const { locale, key, value } = input;
1668
+ if (!isValidLocaleFormat(locale)) {
1669
+ return reject(400, "INVALID_LOCALE_FORMAT", "Invalid locale string parameter.");
1670
+ }
1671
+ if (!isValidKeyFormat(key)) {
1672
+ return reject(
1673
+ 400,
1674
+ "INVALID_KEY_FORMAT",
1675
+ "Invalid key path. Must be 1-200 characters containing alphanumeric, dots, underscores, or hyphens."
1676
+ );
1677
+ }
1678
+ if (!isSafeKeyPath(key)) {
1679
+ return reject(
1680
+ 400,
1681
+ "INVALID_KEY_FORMAT",
1682
+ "Forbidden key path: segments like __proto__, constructor, or prototype are not allowed."
1683
+ );
1684
+ }
1685
+ if (!isValidValueFormat(value)) {
1686
+ return reject(
1687
+ 400,
1688
+ "INVALID_VALUE_FORMAT",
1689
+ "Invalid value. Must be a string less than 10000 characters."
1690
+ );
1691
+ }
1692
+ const accepted = {
1693
+ ok: true,
1694
+ locale,
1695
+ key,
1696
+ value
1697
+ };
1698
+ if (options.force === true) return accepted;
1699
+ const syntaxError = validateMessageSyntax(accepted.value);
1700
+ if (syntaxError) {
1701
+ return reject(422, "MESSAGE_SYNTAX", `Message syntax: ${syntaxError}.`);
1702
+ }
1703
+ const comparison = comparePlaceholders(options.reference, accepted.value);
1704
+ if (!comparison.ok) {
1705
+ const parts = [];
1706
+ if (comparison.missing.length) parts.push(`missing ${comparison.missing.join(", ")}`);
1707
+ if (comparison.extra.length) parts.push(`unexpected ${comparison.extra.join(", ")}`);
1708
+ return {
1709
+ ...reject(
1710
+ 422,
1711
+ "PLACEHOLDER_MISMATCH",
1712
+ `Placeholders differ from the reference: ${parts.join("; ")}.`
1713
+ ),
1714
+ details: { missing: comparison.missing, extra: comparison.extra }
1715
+ };
1716
+ }
1717
+ return accepted;
1718
+ }
1719
+
1649
1720
  // src/server/router.ts
1650
1721
  var DEFAULT_ALLOWED_ORIGINS = ["http://localhost:3010", "http://127.0.0.1:3010"];
1651
1722
  var MAX_BODY_BYTES = 1e6;
@@ -1833,53 +1904,18 @@ function createMutationRouter(mutator, options) {
1833
1904
  await mutator.deleteLocale(basePath, locale);
1834
1905
  return json(200, headers, { success: true, locale });
1835
1906
  }
1836
- if (!isValidLocaleFormat(locale)) {
1837
- return json(400, headers, {
1838
- error: "Invalid locale string parameter.",
1839
- code: "INVALID_LOCALE_FORMAT"
1840
- });
1841
- }
1842
- if (!isValidKeyFormat(key)) {
1843
- return json(400, headers, {
1844
- error: "Invalid key path. Must be 1-200 characters containing alphanumeric, dots, underscores, or hyphens.",
1845
- code: "INVALID_KEY_FORMAT"
1846
- });
1847
- }
1848
- if (!isSafeKeyPath(key)) {
1849
- return json(400, headers, {
1850
- error: "Forbidden key path: segments like __proto__, constructor, or prototype are not allowed.",
1851
- code: "INVALID_KEY_FORMAT"
1852
- });
1853
- }
1854
- if (!isValidValueFormat(value)) {
1855
- return json(400, headers, {
1856
- error: "Invalid value. Must be a string less than 10000 characters.",
1857
- code: "INVALID_VALUE_FORMAT"
1907
+ const verdict = validateWrite(
1908
+ { locale, key, value },
1909
+ { reference: typeof reference === "string" ? reference : null, force: force === true }
1910
+ );
1911
+ if (!verdict.ok) {
1912
+ return json(verdict.status, headers, {
1913
+ error: verdict.error,
1914
+ code: verdict.code,
1915
+ details: verdict.details
1858
1916
  });
1859
1917
  }
1860
- if (force !== true) {
1861
- const syntaxError = validateMessageSyntax(value);
1862
- if (syntaxError) {
1863
- return json(422, headers, {
1864
- error: `Message syntax: ${syntaxError}.`,
1865
- code: "MESSAGE_SYNTAX"
1866
- });
1867
- }
1868
- if (typeof reference === "string") {
1869
- const comparison = comparePlaceholders(reference, value);
1870
- if (!comparison.ok) {
1871
- const parts = [];
1872
- if (comparison.missing.length) parts.push(`missing ${comparison.missing.join(", ")}`);
1873
- if (comparison.extra.length) parts.push(`unexpected ${comparison.extra.join(", ")}`);
1874
- return json(422, headers, {
1875
- error: `Placeholders differ from the reference: ${parts.join("; ")}.`,
1876
- code: "PLACEHOLDER_MISMATCH",
1877
- details: { missing: comparison.missing, extra: comparison.extra }
1878
- });
1879
- }
1880
- }
1881
- }
1882
- await mutator.updateLocaleKey(basePath, locale, key, value);
1918
+ await mutator.updateLocaleKey(basePath, verdict.locale, verdict.key, verdict.value);
1883
1919
  return json(200, headers, { success: true, key, locale });
1884
1920
  }
1885
1921
  return {
@@ -1953,7 +1989,7 @@ var BodyTooLargeError = class extends Error {
1953
1989
  }
1954
1990
  };
1955
1991
  function readNodeBody(req, maxBytes) {
1956
- return new Promise((resolve5, reject) => {
1992
+ return new Promise((resolve5, reject2) => {
1957
1993
  let bodyText = "";
1958
1994
  let settled = false;
1959
1995
  req.on("data", (chunk) => {
@@ -1961,7 +1997,7 @@ function readNodeBody(req, maxBytes) {
1961
1997
  bodyText += chunk.toString();
1962
1998
  if (Buffer.byteLength(bodyText, "utf-8") > maxBytes) {
1963
1999
  settled = true;
1964
- reject(new BodyTooLargeError());
2000
+ reject2(new BodyTooLargeError());
1965
2001
  req.destroy?.();
1966
2002
  }
1967
2003
  });
@@ -1973,7 +2009,7 @@ function readNodeBody(req, maxBytes) {
1973
2009
  req.on("error", (err) => {
1974
2010
  if (settled) return;
1975
2011
  settled = true;
1976
- reject(err);
2012
+ reject2(err);
1977
2013
  });
1978
2014
  });
1979
2015
  }
@@ -2073,10 +2109,11 @@ import { readdirSync, statSync as statSync2 } from "fs";
2073
2109
  import { join as join4 } from "path";
2074
2110
 
2075
2111
  // src/client/watermark.ts
2076
- var DELIM = "\u200D";
2112
+ var DELIM = "\u200C";
2077
2113
  var BIT_0 = "\u200B";
2078
- var BIT_1 = "\u200C";
2114
+ var BIT_1 = "\u200D";
2079
2115
  var ZW_RE = /[\u200B\u200C\u200D]/g;
2116
+ var WATERMARK_RE = /\u200C(?:[\u200B\u200D]{8})+\u200C/g;
2080
2117
  function encodeKey(text, key) {
2081
2118
  const bytes = new TextEncoder().encode(key);
2082
2119
  let bits = "";
@@ -2123,6 +2160,9 @@ function stripWatermark(text) {
2123
2160
  function sanitizeZeroWidth(text) {
2124
2161
  return text.replace(ZW_RE, "");
2125
2162
  }
2163
+ function removeWatermarks(text) {
2164
+ return text.replace(WATERMARK_RE, "");
2165
+ }
2126
2166
  function hasWatermark(text) {
2127
2167
  return text.startsWith(DELIM);
2128
2168
  }
@@ -2372,6 +2412,7 @@ export {
2372
2412
  getLiveLocaleMode,
2373
2413
  hasWatermark,
2374
2414
  isLiveLocaleEnabled,
2415
+ removeWatermarks,
2375
2416
  sanitizeZeroWidth,
2376
2417
  stripWatermark,
2377
2418
  unwrap,