@translation-cms/sync 1.2.1 → 1.2.4

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 (161) hide show
  1. package/dist/bin.d.ts +25 -0
  2. package/dist/bin.d.ts.map +1 -0
  3. package/dist/bin.js +173 -0
  4. package/dist/bin.js.map +1 -0
  5. package/dist/commands/init.d.ts +2 -0
  6. package/dist/commands/init.d.ts.map +1 -0
  7. package/dist/commands/init.js +68 -0
  8. package/dist/commands/init.js.map +1 -0
  9. package/dist/commands/pull.d.ts +3 -0
  10. package/dist/commands/pull.d.ts.map +1 -0
  11. package/dist/commands/pull.js +22 -0
  12. package/dist/commands/pull.js.map +1 -0
  13. package/dist/commands/status.d.ts +3 -0
  14. package/dist/commands/status.d.ts.map +1 -0
  15. package/dist/commands/status.js +46 -0
  16. package/dist/commands/status.js.map +1 -0
  17. package/dist/commands/sync.d.ts +10 -0
  18. package/dist/commands/sync.d.ts.map +1 -0
  19. package/dist/commands/sync.js +20 -0
  20. package/dist/commands/sync.js.map +1 -0
  21. package/dist/commands/watch.d.ts +4 -0
  22. package/dist/commands/watch.d.ts.map +1 -0
  23. package/dist/commands/watch.js +51 -0
  24. package/dist/commands/watch.js.map +1 -0
  25. package/dist/{load-config.d.ts → config/resolve-config.d.ts} +13 -10
  26. package/dist/config/resolve-config.d.ts.map +1 -0
  27. package/dist/{resolve-config.js → config/resolve-config.js} +21 -0
  28. package/dist/config/resolve-config.js.map +1 -0
  29. package/dist/core/api-internals/helpers.d.ts +9 -0
  30. package/dist/core/api-internals/helpers.d.ts.map +1 -0
  31. package/dist/core/api-internals/helpers.js +14 -0
  32. package/dist/core/api-internals/helpers.js.map +1 -0
  33. package/dist/core/api-internals/pull.d.ts +11 -0
  34. package/dist/core/api-internals/pull.d.ts.map +1 -0
  35. package/dist/core/api-internals/pull.js +124 -0
  36. package/dist/core/api-internals/pull.js.map +1 -0
  37. package/dist/core/api-internals/route-config.d.ts +13 -0
  38. package/dist/core/api-internals/route-config.d.ts.map +1 -0
  39. package/dist/core/api-internals/route-config.js +34 -0
  40. package/dist/core/api-internals/route-config.js.map +1 -0
  41. package/dist/core/api-internals/sync.d.ts +12 -0
  42. package/dist/core/api-internals/sync.d.ts.map +1 -0
  43. package/dist/core/api-internals/sync.js +95 -0
  44. package/dist/core/api-internals/sync.js.map +1 -0
  45. package/dist/core/api-internals/types.d.ts +25 -0
  46. package/dist/core/api-internals/types.d.ts.map +1 -0
  47. package/dist/core/api-internals/types.js +5 -0
  48. package/dist/core/api-internals/types.js.map +1 -0
  49. package/dist/core/api.d.ts +11 -0
  50. package/dist/core/api.d.ts.map +1 -0
  51. package/dist/core/api.js +11 -0
  52. package/dist/core/api.js.map +1 -0
  53. package/dist/{sync.d.ts → core/cache.d.ts} +5 -33
  54. package/dist/core/cache.d.ts.map +1 -0
  55. package/dist/core/cache.js +143 -0
  56. package/dist/core/cache.js.map +1 -0
  57. package/dist/core/scanner-internals/ast.d.ts +22 -0
  58. package/dist/core/scanner-internals/ast.d.ts.map +1 -0
  59. package/dist/core/scanner-internals/ast.js +80 -0
  60. package/dist/core/scanner-internals/ast.js.map +1 -0
  61. package/dist/core/scanner-internals/file-walker.d.ts +9 -0
  62. package/dist/core/scanner-internals/file-walker.d.ts.map +1 -0
  63. package/dist/core/scanner-internals/file-walker.js +25 -0
  64. package/dist/core/scanner-internals/file-walker.js.map +1 -0
  65. package/dist/core/scanner-internals/import-resolver.d.ts +13 -0
  66. package/dist/core/scanner-internals/import-resolver.d.ts.map +1 -0
  67. package/dist/core/scanner-internals/import-resolver.js +124 -0
  68. package/dist/core/scanner-internals/import-resolver.js.map +1 -0
  69. package/dist/core/scanner-internals/key-extractor.d.ts +16 -0
  70. package/dist/core/scanner-internals/key-extractor.d.ts.map +1 -0
  71. package/dist/core/scanner-internals/key-extractor.js +172 -0
  72. package/dist/core/scanner-internals/key-extractor.js.map +1 -0
  73. package/dist/core/scanner-internals/route-detector.d.ts +19 -0
  74. package/dist/core/scanner-internals/route-detector.d.ts.map +1 -0
  75. package/dist/core/scanner-internals/route-detector.js +74 -0
  76. package/dist/core/scanner-internals/route-detector.js.map +1 -0
  77. package/dist/core/scanner-internals/types.d.ts +53 -0
  78. package/dist/core/scanner-internals/types.d.ts.map +1 -0
  79. package/dist/core/scanner-internals/types.js +29 -0
  80. package/dist/core/scanner-internals/types.js.map +1 -0
  81. package/dist/core/scanner.d.ts +20 -0
  82. package/dist/core/scanner.d.ts.map +1 -0
  83. package/dist/core/scanner.js +113 -0
  84. package/dist/core/scanner.js.map +1 -0
  85. package/dist/index.d.ts +2 -2
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +2 -2
  88. package/dist/index.js.map +1 -1
  89. package/dist/preview/index.d.ts +38 -0
  90. package/dist/preview/index.d.ts.map +1 -0
  91. package/dist/preview/index.js +114 -0
  92. package/dist/preview/index.js.map +1 -0
  93. package/dist/preview/internals/highlight.d.ts +22 -0
  94. package/dist/preview/internals/highlight.d.ts.map +1 -0
  95. package/dist/preview/internals/highlight.js +153 -0
  96. package/dist/preview/internals/highlight.js.map +1 -0
  97. package/dist/preview/internals/interactions.d.ts +15 -0
  98. package/dist/preview/internals/interactions.d.ts.map +1 -0
  99. package/dist/preview/internals/interactions.js +38 -0
  100. package/dist/preview/internals/interactions.js.map +1 -0
  101. package/dist/preview/internals/locales.d.ts +9 -0
  102. package/dist/preview/internals/locales.d.ts.map +1 -0
  103. package/dist/preview/internals/locales.js +24 -0
  104. package/dist/preview/internals/locales.js.map +1 -0
  105. package/dist/preview/internals/state.d.ts +35 -0
  106. package/dist/preview/internals/state.d.ts.map +1 -0
  107. package/dist/preview/internals/state.js +65 -0
  108. package/dist/preview/internals/state.js.map +1 -0
  109. package/dist/preview/internals/styles.d.ts +8 -0
  110. package/dist/preview/internals/styles.d.ts.map +1 -0
  111. package/dist/preview/internals/styles.js +28 -0
  112. package/dist/preview/internals/styles.js.map +1 -0
  113. package/dist/preview/internals/types.d.ts +49 -0
  114. package/dist/preview/internals/types.d.ts.map +1 -0
  115. package/dist/preview/internals/types.js +5 -0
  116. package/dist/preview/internals/types.js.map +1 -0
  117. package/dist/scaffold/index.d.ts +3 -0
  118. package/dist/scaffold/index.d.ts.map +1 -0
  119. package/dist/scaffold/index.js +3 -0
  120. package/dist/scaffold/index.js.map +1 -0
  121. package/dist/scaffold/intenrals/scaffold.d.ts +7 -0
  122. package/dist/scaffold/intenrals/scaffold.d.ts.map +1 -0
  123. package/dist/scaffold/intenrals/scaffold.js +34 -0
  124. package/dist/scaffold/intenrals/scaffold.js.map +1 -0
  125. package/dist/scaffold/intenrals/templates.d.ts +10 -0
  126. package/dist/scaffold/intenrals/templates.d.ts.map +1 -0
  127. package/dist/{scaffold.js → scaffold/intenrals/templates.js} +10 -38
  128. package/dist/scaffold/intenrals/templates.js.map +1 -0
  129. package/dist/{scaffold.d.ts → scaffold/intenrals/types.d.ts} +4 -2
  130. package/dist/scaffold/intenrals/types.d.ts.map +1 -0
  131. package/dist/scaffold/intenrals/types.js +5 -0
  132. package/dist/scaffold/intenrals/types.js.map +1 -0
  133. package/package.json +7 -7
  134. package/schema.json +64 -0
  135. package/dist/cli-entry.d.ts +0 -3
  136. package/dist/cli-entry.d.ts.map +0 -1
  137. package/dist/cli-entry.js +0 -390
  138. package/dist/cli-entry.js.map +0 -1
  139. package/dist/cli.d.ts +0 -16
  140. package/dist/cli.d.ts.map +0 -1
  141. package/dist/cli.js +0 -16
  142. package/dist/cli.js.map +0 -1
  143. package/dist/load-config.d.ts.map +0 -1
  144. package/dist/load-config.js +0 -24
  145. package/dist/load-config.js.map +0 -1
  146. package/dist/preview.d.ts +0 -88
  147. package/dist/preview.d.ts.map +0 -1
  148. package/dist/preview.js +0 -461
  149. package/dist/preview.js.map +0 -1
  150. package/dist/resolve-config.d.ts +0 -15
  151. package/dist/resolve-config.d.ts.map +0 -1
  152. package/dist/resolve-config.js.map +0 -1
  153. package/dist/scaffold.d.ts.map +0 -1
  154. package/dist/scaffold.js.map +0 -1
  155. package/dist/scanner.d.ts +0 -77
  156. package/dist/scanner.d.ts.map +0 -1
  157. package/dist/scanner.js +0 -555
  158. package/dist/scanner.js.map +0 -1
  159. package/dist/sync.d.ts.map +0 -1
  160. package/dist/sync.js +0 -383
  161. package/dist/sync.js.map +0 -1
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Types and constants used throughout the scanner module.
3
+ */
4
+ // ---------------------------------------------------------------------------
5
+ // Constants
6
+ // ---------------------------------------------------------------------------
7
+ export const DEFAULT_EXCLUDED_DIRS = new Set([
8
+ 'node_modules',
9
+ '.next',
10
+ 'dist',
11
+ '.git',
12
+ 'out',
13
+ '.turbo',
14
+ 'scripts',
15
+ 'packages',
16
+ '.storybook',
17
+ 'coverage',
18
+ ]);
19
+ export const DEFAULT_SOURCE_EXTENSIONS = new Set([
20
+ '.ts',
21
+ '.tsx',
22
+ '.js',
23
+ '.jsx',
24
+ ]);
25
+ /** Return the route string key used for deduplication. */
26
+ export function routeKey(r) {
27
+ return typeof r === 'string' ? r : r.route;
28
+ }
29
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/scanner-internals/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC;IACzC,cAAc;IACd,OAAO;IACP,MAAM;IACN,MAAM;IACN,KAAK;IACL,QAAQ;IACR,SAAS;IACT,UAAU;IACV,YAAY;IACZ,UAAU;CACb,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,GAAG,CAAC;IAC7C,KAAK;IACL,MAAM;IACN,KAAK;IACL,MAAM;CACT,CAAC,CAAC;AAiDH,0DAA0D;AAC1D,MAAM,UAAU,QAAQ,CAAC,CAAe;IACpC,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Main scanner orchestration.
3
+ * Coordinates the multi-pass scanning process for translation keys.
4
+ * Re-exports public types and functions for backward compatibility.
5
+ */
6
+ export { DEFAULT_EXCLUDED_DIRS, DEFAULT_SOURCE_EXTENSIONS, type ScanOptions, type PreviewRoute, type NamespaceMap, routeKey, } from './scanner-internals/types.js';
7
+ export { walkFiles } from './scanner-internals/file-walker.js';
8
+ export { isRedirectOnlyPage, filePathToRoute, } from './scanner-internals/route-detector.js';
9
+ export { extractKeysFromFile } from './scanner-internals/key-extractor.js';
10
+ import { type ScanOptions, type NamespaceMap } from './scanner-internals/types.js';
11
+ /**
12
+ * Scans the project for translation keys and optionally merges external preview routes.
13
+ *
14
+ * Pass 1: Scan all files, extracting keys and associating them with page/layout routes.
15
+ * Pass 2: For each page/layout, walk its import tree to assign keys in deeply-nested
16
+ * components to the correct preview route.
17
+ * Pass 3: Merge external preview routes from database or config.
18
+ */
19
+ export declare function scanProject(root: string, opts?: ScanOptions): NamespaceMap;
20
+ //# sourceMappingURL=scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/core/scanner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EACH,qBAAqB,EACrB,yBAAyB,EACzB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,QAAQ,GACX,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAC;AAC/D,OAAO,EACH,kBAAkB,EAClB,eAAe,GAClB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAG3E,OAAO,EAGH,KAAK,WAAW,EAChB,KAAK,YAAY,EAEpB,MAAM,8BAA8B,CAAC;AAatC;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,YAAY,CAoG1E"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Main scanner orchestration.
3
+ * Coordinates the multi-pass scanning process for translation keys.
4
+ * Re-exports public types and functions for backward compatibility.
5
+ */
6
+ import path from 'path';
7
+ // Re-export types and constants
8
+ export { DEFAULT_EXCLUDED_DIRS, DEFAULT_SOURCE_EXTENSIONS, routeKey, } from './scanner-internals/types.js';
9
+ // Re-export public functions from submodules
10
+ export { walkFiles } from './scanner-internals/file-walker.js';
11
+ export { isRedirectOnlyPage, filePathToRoute, } from './scanner-internals/route-detector.js';
12
+ export { extractKeysFromFile } from './scanner-internals/key-extractor.js';
13
+ // Import utilities needed for orchestration
14
+ import { DEFAULT_EXCLUDED_DIRS, DEFAULT_SOURCE_EXTENSIONS, routeKey, } from './scanner-internals/types.js';
15
+ import { walkFiles } from './scanner-internals/file-walker.js';
16
+ import { filePathToRoute } from './scanner-internals/route-detector.js';
17
+ import { extractKeysFromFile } from './scanner-internals/key-extractor.js';
18
+ import { extractLocalImports, getLayoutChain, } from './scanner-internals/import-resolver.js';
19
+ // ---------------------------------------------------------------------------
20
+ // Project scan
21
+ // ---------------------------------------------------------------------------
22
+ /**
23
+ * Scans the project for translation keys and optionally merges external preview routes.
24
+ *
25
+ * Pass 1: Scan all files, extracting keys and associating them with page/layout routes.
26
+ * Pass 2: For each page/layout, walk its import tree to assign keys in deeply-nested
27
+ * components to the correct preview route.
28
+ * Pass 3: Merge external preview routes from database or config.
29
+ */
30
+ export function scanProject(root, opts) {
31
+ const excludedDirs = new Set([
32
+ ...DEFAULT_EXCLUDED_DIRS,
33
+ ...(opts?.excludedDirs ?? []),
34
+ ]);
35
+ const sourceExtensions = opts?.sourceExtensions
36
+ ? new Set(opts.sourceExtensions)
37
+ : DEFAULT_SOURCE_EXTENSIONS;
38
+ const routeParams = opts?.routeParams;
39
+ const pathMappings = opts?.pathMappings;
40
+ const projectRoot = opts?.projectRoot ?? root;
41
+ const result = {};
42
+ let fileCount = 0;
43
+ // Pass 1: scan all files normally
44
+ const routeByFile = new Map();
45
+ for (const file of walkFiles(root, excludedDirs, sourceExtensions)) {
46
+ const routes = filePathToRoute(root, file);
47
+ routeByFile.set(file, routes);
48
+ extractKeysFromFile(file, routes, result);
49
+ fileCount++;
50
+ }
51
+ // Pass 2: for each page/layout file, transitively walk its import tree and
52
+ // re-scan every non-page/layout component with the same routes. This ensures
53
+ // keys used in deeply-nested components (e.g. layout → Nav → NavItem) are
54
+ // associated with the correct preview routes.
55
+ const scannedWithRoute = new Set(); // "file::route" pairs already done
56
+ for (const [file, routes] of routeByFile) {
57
+ if (routes.length === 0)
58
+ continue;
59
+ const isPage = path.basename(file).startsWith('page.');
60
+ const layoutChain = isPage
61
+ ? getLayoutChain(root, file, sourceExtensions)
62
+ : [];
63
+ for (const layout of layoutChain) {
64
+ const layoutKey = `${layout}::${routes.join('|')}`;
65
+ if (!scannedWithRoute.has(layoutKey)) {
66
+ scannedWithRoute.add(layoutKey);
67
+ extractKeysFromFile(layout, routes, result);
68
+ }
69
+ }
70
+ const queue = [file, ...layoutChain];
71
+ while (queue.length > 0) {
72
+ const current = queue.shift();
73
+ const imports = extractLocalImports(current, sourceExtensions, projectRoot, pathMappings);
74
+ if (imports.length > 0) {
75
+ console.log(`[Pass 2] ${path.relative(root, current)} (${routes.join(', ')}) imports: ${imports.map(i => path.relative(root, i)).join(', ')}`);
76
+ }
77
+ for (const imported of imports) {
78
+ const key = `${imported}::${routes.join('|')}`;
79
+ if (scannedWithRoute.has(key))
80
+ continue;
81
+ scannedWithRoute.add(key);
82
+ if (!routeByFile.has(imported)) {
83
+ console.log(`[Pass 2] Skipping ${path.relative(root, imported)} — not in walk result`);
84
+ continue;
85
+ }
86
+ if (routeByFile.get(imported).length > 0)
87
+ continue;
88
+ console.log(`[Pass 2] Re-scanning ${path.relative(root, imported)} with routes: ${routes.join(', ')}`);
89
+ extractKeysFromFile(imported, routes, result);
90
+ queue.push(imported);
91
+ }
92
+ }
93
+ }
94
+ // Pass 3: upgrade plain dynamic routes to { route, params } objects using routeParams
95
+ if (routeParams) {
96
+ for (const [, keyMap] of Object.entries(result)) {
97
+ for (const [key, routes] of keyMap.entries()) {
98
+ const upgraded = routes.map(r => {
99
+ const rk = routeKey(r);
100
+ const params = routeParams[rk];
101
+ if (params && typeof r === 'string') {
102
+ return { route: rk, params };
103
+ }
104
+ return r;
105
+ });
106
+ keyMap.set(key, upgraded);
107
+ }
108
+ }
109
+ }
110
+ console.log(`Scanned ${fileCount} files.`);
111
+ return result;
112
+ }
113
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../src/core/scanner.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { initPreviewListener, cleanupPreviewListener, registerPreviewAction, unregisterPreviewAction, type PreviewListenerOptions, } from './preview.js';
2
- export { type TranslationsConfig } from './load-config.js';
1
+ export { initPreviewListener, cleanupPreviewListener, type PreviewListenerOptions, } from './preview/index.js';
2
+ export { type TranslationsConfig } from './config/resolve-config.js';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACH,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,uBAAuB,EACvB,KAAK,sBAAsB,GAC9B,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACH,mBAAmB,EACnB,sBAAsB,EACtB,KAAK,sBAAsB,GAC9B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,4BAA4B,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- // In-context preview listener
2
- export { initPreviewListener, cleanupPreviewListener, registerPreviewAction, unregisterPreviewAction, } from './preview.js';
1
+ // Browser: in-context preview listener
2
+ export { initPreviewListener, cleanupPreviewListener, } from './preview/index.js';
3
3
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,OAAO,EACH,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,uBAAuB,GAE1B,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,OAAO,EACH,mBAAmB,EACnB,sBAAsB,GAEzB,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Translation CMS — Preview Listener
3
+ *
4
+ * Add this to your client app to enable live in-context preview of translations
5
+ * from the CMS. No per-component setup required.
6
+ *
7
+ * Usage in Next.js root layout:
8
+ * ```tsx
9
+ * 'use client';
10
+ * import { useEffect } from 'react';
11
+ * import { initPreviewListener, cleanupPreviewListener } from '@translation-cms/sync/preview';
12
+ *
13
+ * export function CMSPreview() {
14
+ * useEffect(() => {
15
+ * initPreviewListener({ onLocaleSwitch: (locale) => i18n.changeLanguage(locale) });
16
+ * return () => cleanupPreviewListener();
17
+ * }, []);
18
+ * return null;
19
+ * }
20
+ * ```
21
+ *
22
+ * Precise highlighting (recommended):
23
+ * Add `data-cms-key="namespace:key"` to elements that render a translation.
24
+ * The listener will target those elements directly, bypassing text-search heuristics.
25
+ * ```tsx
26
+ * <h1 data-cms-key="blog:post.title">{t('blog:post.title')}</h1>
27
+ * ```
28
+ */
29
+ export type { PreviewListenerOptions } from './internals/types.js';
30
+ import type { PreviewListenerOptions } from './internals/types.js';
31
+ /**
32
+ * Initialize the preview listener.
33
+ * Safe to call multiple times — only initialises once.
34
+ */
35
+ export declare function initPreviewListener(options?: PreviewListenerOptions): void;
36
+ /** Remove the preview listener and clear all highlights. */
37
+ export declare function cleanupPreviewListener(): void;
38
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/preview/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,YAAY,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAenE,OAAO,KAAK,EAIR,sBAAsB,EACzB,MAAM,sBAAsB,CAAC;AAM9B;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,IAAI,CA4B1E;AAED,4DAA4D;AAC5D,wBAAgB,sBAAsB,IAAI,IAAI,CAO7C"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Translation CMS — Preview Listener
3
+ *
4
+ * Add this to your client app to enable live in-context preview of translations
5
+ * from the CMS. No per-component setup required.
6
+ *
7
+ * Usage in Next.js root layout:
8
+ * ```tsx
9
+ * 'use client';
10
+ * import { useEffect } from 'react';
11
+ * import { initPreviewListener, cleanupPreviewListener } from '@translation-cms/sync/preview';
12
+ *
13
+ * export function CMSPreview() {
14
+ * useEffect(() => {
15
+ * initPreviewListener({ onLocaleSwitch: (locale) => i18n.changeLanguage(locale) });
16
+ * return () => cleanupPreviewListener();
17
+ * }, []);
18
+ * return null;
19
+ * }
20
+ * ```
21
+ *
22
+ * Precise highlighting (recommended):
23
+ * Add `data-cms-key="namespace:key"` to elements that render a translation.
24
+ * The listener will target those elements directly, bypassing text-search heuristics.
25
+ * ```tsx
26
+ * <h1 data-cms-key="blog:post.title">{t('blog:post.title')}</h1>
27
+ * ```
28
+ */
29
+ // Internal imports
30
+ import * as state from './internals/state.js';
31
+ import { highlightTranslationKey, clearHighlights, updateLiveText, } from './internals/highlight.js';
32
+ import { handleLocaleSwitch } from './internals/locales.js';
33
+ import { injectInteractionBlocker, removeInteractionBlocker, } from './internals/interactions.js';
34
+ import { injectStyles } from './internals/styles.js';
35
+ // ---------------------------------------------------------------------------
36
+ // Public functions
37
+ // ---------------------------------------------------------------------------
38
+ /**
39
+ * Initialize the preview listener.
40
+ * Safe to call multiple times — only initialises once.
41
+ */
42
+ export function initPreviewListener(options) {
43
+ if (state.isInitialized)
44
+ return;
45
+ const isDev = typeof window !== 'undefined' &&
46
+ (window.location.hostname === 'localhost' ||
47
+ window.location.hostname === '127.0.0.1' ||
48
+ window.location.search.includes('preview=true'));
49
+ if (!isDev) {
50
+ console.log('[CMS Preview] Not in development mode, skipping...');
51
+ return;
52
+ }
53
+ state.initializeState(options ?? {});
54
+ state.setIsInitialized(true);
55
+ window.addEventListener('message', handleMessage);
56
+ injectStyles();
57
+ // Only inject the interaction blocker when running inside an iframe
58
+ // (i.e. the CMS preview context). Opening the page directly on localhost
59
+ // should never have interactions blocked.
60
+ if (window.top !== window) {
61
+ injectInteractionBlocker();
62
+ }
63
+ console.log('%c[CMS Preview] Ready', 'color: #10b981; font-weight: bold;');
64
+ }
65
+ /** Remove the preview listener and clear all highlights. */
66
+ export function cleanupPreviewListener() {
67
+ if (!state.isInitialized)
68
+ return;
69
+ window.removeEventListener('message', handleMessage);
70
+ removeInteractionBlocker();
71
+ clearHighlights();
72
+ state.resetState();
73
+ console.log('[CMS Preview] Listener cleaned up');
74
+ }
75
+ // ---------------------------------------------------------------------------
76
+ // Message handler
77
+ // ---------------------------------------------------------------------------
78
+ function handleMessage(event) {
79
+ const { type } = (event.data ?? {});
80
+ if (type === 'CMS_HIGHLIGHT_KEY') {
81
+ const { key, value, locale } = event.data;
82
+ console.log(`[CMS Preview] Highlighting: ${key} (${locale}) = "${value}"`);
83
+ // Only switch locale when it actually changed. Switching unconditionally
84
+ // triggers a re-render in the consuming app, which replaces DOM nodes
85
+ // and immediately removes the highlight class we just applied.
86
+ if (locale && locale !== state.activeLocale) {
87
+ state.setActiveLocale(locale);
88
+ handleLocaleSwitch(locale);
89
+ }
90
+ state.setCurrentLiveValue(null);
91
+ state.setPendingHighlight({ key, value });
92
+ highlightTranslationKey(value, key);
93
+ // If no elements were found (e.g. dialog still animating open), schedule
94
+ // a backup retry after the animation should have settled.
95
+ if (state.highlightedElements.length === 0) {
96
+ setTimeout(() => {
97
+ if (state.pendingHighlight?.key === key) {
98
+ console.log(`[CMS Preview] Retrying highlight for: ${key}`);
99
+ highlightTranslationKey(value, key);
100
+ }
101
+ }, 600);
102
+ }
103
+ }
104
+ else if (type === 'CMS_LIVE_UPDATE') {
105
+ const { oldValue, newValue } = event.data;
106
+ updateLiveText(oldValue, newValue);
107
+ }
108
+ else if (type === 'CMS_SWITCH_LOCALE') {
109
+ const { locale } = event.data;
110
+ state.setActiveLocale(locale);
111
+ handleLocaleSwitch(locale);
112
+ }
113
+ }
114
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/preview/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Translation highlighting logic.
3
+ * Handles searching for and highlighting elements that display translations.
4
+ */
5
+ /**
6
+ * Find and highlight DOM elements that display the given translation.
7
+ *
8
+ * Search order:
9
+ * 0. data-cms-key attribute match (precise — skips all heuristics)
10
+ * 1. Exact text node match
11
+ * 2. Element content match (handles split siblings)
12
+ * 3. Normalized text node match (strips {{interpolation}} placeholders)
13
+ * 4. Normalized element match
14
+ * 5. Key fallback (when value is empty — i18next renders the key as text)
15
+ */
16
+ export declare function highlightTranslationKey(value: string, key: string): void;
17
+ export declare function clearHighlights(): void;
18
+ /**
19
+ * Update highlighted elements with live translation changes.
20
+ */
21
+ export declare function updateLiveText(oldValue: string, newValue: string): void;
22
+ //# sourceMappingURL=highlight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"highlight.d.ts","sourceRoot":"","sources":["../../../src/preview/internals/highlight.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAoEH;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAyExE;AAeD,wBAAgB,eAAe,IAAI,IAAI,CAMtC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAuBvE"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Translation highlighting logic.
3
+ * Handles searching for and highlighting elements that display translations.
4
+ */
5
+ import * as state from './state.js';
6
+ function normalizeForSearch(value) {
7
+ return value
8
+ .replace(/\{\{[^}]+\}\}/g, '')
9
+ .replace(/\{[^}]+\}/g, '')
10
+ .replace(/\s+/g, ' ')
11
+ .trim();
12
+ }
13
+ /**
14
+ * Strategy 0 (precise): find elements with data-cms-key="key" attribute.
15
+ * When present, this is always preferred over text-content heuristics.
16
+ */
17
+ function findByDataAttribute(key) {
18
+ return Array.from(document.querySelectorAll(`[data-cms-key="${CSS.escape(key)}"]`));
19
+ }
20
+ /** Strategy 1: walk text nodes, return parent elements whose text includes value. */
21
+ function findByTextNodes(searchValue) {
22
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null);
23
+ const results = [];
24
+ let node;
25
+ while ((node = walker.nextNode())) {
26
+ if (node.textContent?.includes(searchValue)) {
27
+ const el = node.parentElement;
28
+ if (el && !el.classList.contains('cms-preview-highlight')) {
29
+ results.push(el);
30
+ }
31
+ }
32
+ }
33
+ return results;
34
+ }
35
+ /**
36
+ * Strategy 2: find the most specific element whose combined textContent
37
+ * includes the value. Works when text is split across siblings.
38
+ */
39
+ function findByElementContent(searchValue) {
40
+ const all = Array.from(document.body.querySelectorAll('*'));
41
+ let best = null;
42
+ let bestLen = Infinity;
43
+ for (const el of all) {
44
+ if (el.textContent?.includes(searchValue) &&
45
+ !el.classList.contains('cms-preview-highlight')) {
46
+ const len = el.textContent.length;
47
+ if (len < bestLen) {
48
+ bestLen = len;
49
+ best = el;
50
+ }
51
+ }
52
+ }
53
+ return best ? [best] : [];
54
+ }
55
+ /**
56
+ * Find and highlight DOM elements that display the given translation.
57
+ *
58
+ * Search order:
59
+ * 0. data-cms-key attribute match (precise — skips all heuristics)
60
+ * 1. Exact text node match
61
+ * 2. Element content match (handles split siblings)
62
+ * 3. Normalized text node match (strips {{interpolation}} placeholders)
63
+ * 4. Normalized element match
64
+ * 5. Key fallback (when value is empty — i18next renders the key as text)
65
+ */
66
+ export function highlightTranslationKey(value, key) {
67
+ clearHighlights();
68
+ // --- Strategy 0: data-cms-key attribute (precise) -----------------------
69
+ const byAttr = findByDataAttribute(key);
70
+ if (byAttr.length > 0) {
71
+ applyHighlights(byAttr, 'data-cms-key attribute');
72
+ return;
73
+ }
74
+ // --- Heuristic fallback strategies --------------------------------------
75
+ const normalized = value ? normalizeForSearch(value) : '';
76
+ const hasInterpolation = value ? normalized !== value : false;
77
+ const candidates = [];
78
+ if (value && value.trim() !== '') {
79
+ candidates.push({ label: 'exact text node', search: value, fn: findByTextNodes }, {
80
+ label: 'element content',
81
+ search: value,
82
+ fn: findByElementContent,
83
+ });
84
+ if (hasInterpolation && normalized) {
85
+ candidates.push({
86
+ label: 'normalized text node',
87
+ search: normalized,
88
+ fn: findByTextNodes,
89
+ }, {
90
+ label: 'normalized element',
91
+ search: normalized,
92
+ fn: findByElementContent,
93
+ });
94
+ }
95
+ }
96
+ else {
97
+ const keyWithoutNs = key.includes(':')
98
+ ? key.split(':').slice(1).join(':')
99
+ : key;
100
+ candidates.push({ label: 'key (full)', search: key, fn: findByTextNodes }, { label: 'key (no ns)', search: keyWithoutNs, fn: findByTextNodes }, { label: 'key element', search: key, fn: findByElementContent }, {
101
+ label: 'key element (no ns)',
102
+ search: keyWithoutNs,
103
+ fn: findByElementContent,
104
+ });
105
+ }
106
+ for (const { label, search, fn } of candidates) {
107
+ const found = fn(search);
108
+ if (found.length > 0) {
109
+ state.setCurrentSearchValue(search);
110
+ applyHighlights(found, label);
111
+ return;
112
+ }
113
+ }
114
+ console.warn(`[CMS Preview] No elements found for key: "${key}" value: "${value}"`);
115
+ }
116
+ function applyHighlights(elements, matchLabel) {
117
+ console.log(`[CMS Preview] Highlighting ${elements.length} element(s) via ${matchLabel}`);
118
+ elements.forEach((el, i) => {
119
+ el.classList.add('cms-preview-highlight');
120
+ state.addHighlightedElement(el);
121
+ if (i === 0) {
122
+ el.scrollIntoView({ behavior: 'smooth', block: 'center' });
123
+ }
124
+ });
125
+ }
126
+ export function clearHighlights() {
127
+ for (const el of state.highlightedElements) {
128
+ el.classList.remove('cms-preview-highlight');
129
+ }
130
+ state.setHighlightedElements([]);
131
+ state.setCurrentSearchValue(null);
132
+ }
133
+ /**
134
+ * Update highlighted elements with live translation changes.
135
+ */
136
+ export function updateLiveText(oldValue, newValue) {
137
+ if (state.highlightedElements.length === 0)
138
+ return;
139
+ const searchFor = state.currentLiveValue ??
140
+ (oldValue || state.currentSearchValue) ??
141
+ oldValue;
142
+ for (const el of state.highlightedElements) {
143
+ const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);
144
+ let node;
145
+ while ((node = walker.nextNode())) {
146
+ if (node.nodeValue?.includes(searchFor)) {
147
+ node.nodeValue = node.nodeValue.replace(searchFor, newValue);
148
+ }
149
+ }
150
+ }
151
+ state.setCurrentLiveValue(newValue);
152
+ }
153
+ //# sourceMappingURL=highlight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"highlight.js","sourceRoot":"","sources":["../../../src/preview/internals/highlight.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Interaction blocking for preview mode.
3
+ * Prevents users from clicking links or submitting forms while previewing.
4
+ */
5
+ /**
6
+ * Inject a transparent full-page overlay that absorbs pointer events,
7
+ * and register capture-phase listeners for belt-and-suspenders blocking
8
+ * (covers keyboard-activated links/buttons the overlay doesn't intercept).
9
+ */
10
+ export declare function injectInteractionBlocker(): void;
11
+ /**
12
+ * Remove interaction blocker and event listeners.
13
+ */
14
+ export declare function removeInteractionBlocker(): void;
15
+ //# sourceMappingURL=interactions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactions.d.ts","sourceRoot":"","sources":["../../../src/preview/internals/interactions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAaH;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAU/C;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAK/C"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Interaction blocking for preview mode.
3
+ * Prevents users from clicking links or submitting forms while previewing.
4
+ */
5
+ import * as state from './state.js';
6
+ /**
7
+ * Capture-phase handler that swallows all clicks and form submits.
8
+ * Prevents links from navigating and buttons from firing actions.
9
+ */
10
+ function blockInteraction(e) {
11
+ e.preventDefault();
12
+ e.stopPropagation();
13
+ }
14
+ /**
15
+ * Inject a transparent full-page overlay that absorbs pointer events,
16
+ * and register capture-phase listeners for belt-and-suspenders blocking
17
+ * (covers keyboard-activated links/buttons the overlay doesn't intercept).
18
+ */
19
+ export function injectInteractionBlocker() {
20
+ const overlay = document.createElement('div');
21
+ overlay.id = 'cms-preview-blocker';
22
+ overlay.style.cssText =
23
+ 'position:fixed;inset:0;z-index:9997;pointer-events:all;cursor:default;';
24
+ document.body.appendChild(overlay);
25
+ state.setInteractionBlockerOverlay(overlay);
26
+ document.addEventListener('click', blockInteraction, true);
27
+ document.addEventListener('submit', blockInteraction, true);
28
+ }
29
+ /**
30
+ * Remove interaction blocker and event listeners.
31
+ */
32
+ export function removeInteractionBlocker() {
33
+ document.removeEventListener('click', blockInteraction, true);
34
+ document.removeEventListener('submit', blockInteraction, true);
35
+ state.interactionBlockerOverlay?.remove();
36
+ state.setInteractionBlockerOverlay(null);
37
+ }
38
+ //# sourceMappingURL=interactions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactions.js","sourceRoot":"","sources":["../../../src/preview/internals/interactions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AAEpC;;;GAGG;AACH,SAAS,gBAAgB,CAAC,CAAQ;IAC9B,CAAC,CAAC,cAAc,EAAE,CAAC;IACnB,CAAC,CAAC,eAAe,EAAE,CAAC;AACxB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB;IACpC,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAC9C,OAAO,CAAC,EAAE,GAAG,qBAAqB,CAAC;IACnC,OAAO,CAAC,KAAK,CAAC,OAAO;QACjB,wEAAwE,CAAC;IAC7E,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACnC,KAAK,CAAC,4BAA4B,CAAC,OAAO,CAAC,CAAC;IAE5C,QAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,CAAC,CAAC;IAC3D,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,IAAI,CAAC,CAAC;AAChE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,wBAAwB;IACpC,QAAQ,CAAC,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,CAAC,CAAC;IAC9D,QAAQ,CAAC,mBAAmB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,IAAI,CAAC,CAAC;IAC/D,KAAK,CAAC,yBAAyB,EAAE,MAAM,EAAE,CAAC;IAC1C,KAAK,CAAC,4BAA4B,CAAC,IAAI,CAAC,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Locale switching logic for preview listener.
3
+ */
4
+ /**
5
+ * Handle a locale switch request from the CMS.
6
+ * Tries the provided callback first, then falls back to window.__cmsSetLocale.
7
+ */
8
+ export declare function handleLocaleSwitch(locale: string): void;
9
+ //# sourceMappingURL=locales.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/preview/internals/locales.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAmBvD"}