@kokimoki/kit 1.6.7 → 1.7.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.
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ /**
3
+ * Renders the dev frame HTML page - a multi-window view for development
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.renderDevFrame = renderDevFrame;
7
+ const styles_1 = require("./styles");
8
+ /**
9
+ * Generate URL for a dev frame iframe
10
+ */
11
+ function generateFrameUrl(key, context) {
12
+ const contextBase64 = btoa(JSON.stringify(context));
13
+ return `?key=${encodeURIComponent(key)}&context=${encodeURIComponent(contextBase64)}`;
14
+ }
15
+ /**
16
+ * Generate the frame HTML for a single dev frame cell
17
+ */
18
+ function renderCell(cell, isDuplicate) {
19
+ if (isDuplicate) {
20
+ return `
21
+ <div class="dev-frame dev-frame-error" data-frame-key="${cell.label}">
22
+ <div class="dev-frame-header">
23
+ <span class="dev-frame-title">${cell.label}</span>
24
+ </div>
25
+ <div class="dev-frame-warning">
26
+ <div class="dev-frame-warning-icon">⚠️</div>
27
+ <div class="dev-frame-warning-title">Duplicate Label</div>
28
+ <div class="dev-frame-warning-message">
29
+ Label "<strong>${cell.label}</strong>" is already used by another cell.<br>
30
+ Each cell must have a unique label.
31
+ </div>
32
+ </div>
33
+ </div>
34
+ `;
35
+ }
36
+ const url = generateFrameUrl(cell.label, cell.clientContext);
37
+ return `
38
+ <div class="dev-frame" data-frame-key="${cell.label}">
39
+ <div class="dev-frame-header">
40
+ <span class="dev-frame-title">${cell.label}</span>
41
+ <a class="dev-frame-link" href="${url}" target="_blank" rel="noopener noreferrer">New tab</a>
42
+ <button class="dev-frame-btn" onclick="clearFrameStorage('${cell.label}')">Reset</button>
43
+ </div>
44
+ <div class="dev-frame-content">
45
+ <div class="dev-frame-error-overlay" id="error-${cell.label}" style="display: none;">
46
+ <div class="dev-frame-error-icon">⚠️</div>
47
+ <div class="dev-frame-error-title">Error</div>
48
+ <pre class="dev-frame-error-message" id="error-msg-${cell.label}"></pre>
49
+ <button class="dev-frame-error-btn" onclick="reloadFrame('${cell.label}')">Reload</button>
50
+ </div>
51
+ <iframe
52
+ id="frame-${cell.label}"
53
+ title="${cell.label}"
54
+ src="${url}"
55
+ ></iframe>
56
+ </div>
57
+ </div>
58
+ `;
59
+ }
60
+ /**
61
+ * Generate the HTML for a row of frames, tracking seen labels for duplicate detection
62
+ */
63
+ function renderRow(cells, seenLabels) {
64
+ const cellsHtml = cells
65
+ .map((cell) => {
66
+ const isDuplicate = seenLabels.has(cell.label);
67
+ seenLabels.add(cell.label);
68
+ return renderCell(cell, isDuplicate);
69
+ })
70
+ .join("\n");
71
+ return `<div class="dev-row">${cellsHtml}</div>`;
72
+ }
73
+ /**
74
+ * Generate the JavaScript for dev frame functionality
75
+ */
76
+ function renderScript() {
77
+ return `
78
+ <script>
79
+ // Track iframe references
80
+ const frames = {};
81
+
82
+ // Initialize frame references after DOM loads
83
+ document.querySelectorAll('.dev-frame iframe').forEach(iframe => {
84
+ const key = iframe.id.replace('frame-', '');
85
+ frames[key] = iframe;
86
+ });
87
+
88
+ // Handle messages from iframes
89
+ window.addEventListener('message', (event) => {
90
+ // Find which iframe sent this message
91
+ const frameEntry = Object.entries(frames).find(
92
+ ([_, iframe]) => iframe.contentWindow === event.source
93
+ );
94
+
95
+ if (!frameEntry) return;
96
+ const [key, iframe] = frameEntry;
97
+
98
+ // If iframe requests its client key (sends appId), respond with key
99
+ if (event.data && event.data.appId) {
100
+ iframe.contentWindow.postMessage({ clientKey: key }, '*');
101
+ }
102
+
103
+ // If iframe signals error, show error overlay
104
+ if (event.data && event.data.type === 'km:error') {
105
+ const errorOverlay = document.getElementById('error-' + key);
106
+ const errorMsg = document.getElementById('error-msg-' + key);
107
+
108
+ if (errorOverlay) errorOverlay.style.display = 'flex';
109
+ if (errorMsg) {
110
+ const stack = event.data.stack || event.data.message;
111
+ errorMsg.textContent = stack;
112
+ }
113
+ }
114
+ });
115
+
116
+ // Clear storage for a specific frame
117
+ function clearFrameStorage(key) {
118
+ const iframe = frames[key];
119
+ if (iframe && iframe.contentWindow) {
120
+ iframe.contentWindow.postMessage('km:clearStorage', '*');
121
+ }
122
+ }
123
+
124
+ // Reload a specific frame
125
+ function reloadFrame(key) {
126
+ const iframe = frames[key];
127
+ const errorOverlay = document.getElementById('error-' + key);
128
+
129
+ if (errorOverlay) errorOverlay.style.display = 'none';
130
+ if (iframe) {
131
+ iframe.src = iframe.src;
132
+ }
133
+ }
134
+ </script>
135
+ `;
136
+ }
137
+ /**
138
+ * Render the complete dev frame HTML page
139
+ */
140
+ function renderDevFrame(config) {
141
+ const { title = "Dev View", rows } = config;
142
+ // Track seen labels across all rows for duplicate detection
143
+ const seenLabels = new Set();
144
+ const rowsHtml = rows.map((row) => renderRow(row, seenLabels)).join("\n");
145
+ return `<!DOCTYPE html>
146
+ <html lang="en">
147
+ <head>
148
+ <meta charset="UTF-8">
149
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
150
+ <title>${title}</title>
151
+ <style>${styles_1.devFrameStyles}</style>
152
+ </head>
153
+ <body>
154
+ <div class="dev-container">
155
+ ${rowsHtml}
156
+ </div>
157
+ ${renderScript()}
158
+ </body>
159
+ </html>`;
160
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * CSS styles for the dev frame
3
+ */
4
+ export declare const devFrameStyles = "\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n html, body {\n height: 100%;\n overflow: hidden;\n font-family: system-ui, -apple-system, sans-serif;\n }\n\n .dev-container {\n display: flex;\n flex-direction: column;\n gap: 2px;\n height: 100dvh;\n background: #E4D8B4;\n }\n\n .dev-row {\n display: flex;\n flex: 1;\n gap: 2px;\n min-height: 0;\n }\n\n .dev-frame {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n background: #ECE3CA;\n }\n\n .dev-frame-header {\n display: flex;\n justify-content: center;\n align-items: center;\n gap: 12px;\n padding: 8px;\n font-size: 14px;\n color: #793205;\n }\n\n .dev-frame-title {\n font-weight: 600;\n }\n\n .dev-frame-link {\n color: #793205;\n text-decoration: none;\n }\n\n .dev-frame-link:hover {\n text-decoration: underline;\n }\n\n .dev-frame-btn {\n background: none;\n border: none;\n color: #793205;\n cursor: pointer;\n font-size: 14px;\n font-family: inherit;\n }\n\n .dev-frame-btn:hover {\n text-decoration: underline;\n }\n\n .dev-frame-content {\n position: relative;\n flex: 1;\n min-height: 0;\n }\n\n .dev-frame iframe {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: none;\n }\n\n .dev-frame-error .dev-frame-header {\n background: #fee2e2;\n color: #991b1b;\n }\n\n .dev-frame-warning {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 20px;\n background: #fef3c7;\n color: #92400e;\n text-align: center;\n }\n\n .dev-frame-warning-icon {\n font-size: 48px;\n margin-bottom: 12px;\n }\n\n .dev-frame-warning-title {\n font-size: 18px;\n font-weight: 600;\n margin-bottom: 8px;\n color: #b45309;\n }\n\n .dev-frame-warning-message {\n font-size: 14px;\n line-height: 1.5;\n color: #92400e;\n }\n\n .dev-frame-error-overlay {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 20px;\n background: linear-gradient(135deg, #2d1f1f 0%, #1f1a1a 100%);\n z-index: 15;\n color: #fca5a5;\n text-align: center;\n overflow: auto;\n }\n\n .dev-frame-error-icon {\n font-size: 36px;\n margin-bottom: 12px;\n }\n\n .dev-frame-error-title {\n font-size: 18px;\n font-weight: 600;\n margin-bottom: 12px;\n color: #f87171;\n }\n\n .dev-frame-error-message {\n font-size: 12px;\n line-height: 1.5;\n color: #fca5a5;\n background: rgba(0, 0, 0, 0.3);\n padding: 12px;\n border-radius: 8px;\n max-width: 100%;\n max-height: 200px;\n overflow: auto;\n white-space: pre-wrap;\n word-break: break-word;\n text-align: left;\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n }\n\n .dev-frame-error-btn {\n margin-top: 16px;\n padding: 8px 16px;\n background: #ef4444;\n color: white;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n font-size: 14px;\n font-family: inherit;\n transition: background 0.2s;\n }\n\n .dev-frame-error-btn:hover {\n background: #dc2626;\n }\n";
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ /**
3
+ * CSS styles for the dev frame
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.devFrameStyles = void 0;
7
+ exports.devFrameStyles = `
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ html, body {
15
+ height: 100%;
16
+ overflow: hidden;
17
+ font-family: system-ui, -apple-system, sans-serif;
18
+ }
19
+
20
+ .dev-container {
21
+ display: flex;
22
+ flex-direction: column;
23
+ gap: 2px;
24
+ height: 100dvh;
25
+ background: #E4D8B4;
26
+ }
27
+
28
+ .dev-row {
29
+ display: flex;
30
+ flex: 1;
31
+ gap: 2px;
32
+ min-height: 0;
33
+ }
34
+
35
+ .dev-frame {
36
+ display: flex;
37
+ flex-direction: column;
38
+ flex: 1;
39
+ min-width: 0;
40
+ background: #ECE3CA;
41
+ }
42
+
43
+ .dev-frame-header {
44
+ display: flex;
45
+ justify-content: center;
46
+ align-items: center;
47
+ gap: 12px;
48
+ padding: 8px;
49
+ font-size: 14px;
50
+ color: #793205;
51
+ }
52
+
53
+ .dev-frame-title {
54
+ font-weight: 600;
55
+ }
56
+
57
+ .dev-frame-link {
58
+ color: #793205;
59
+ text-decoration: none;
60
+ }
61
+
62
+ .dev-frame-link:hover {
63
+ text-decoration: underline;
64
+ }
65
+
66
+ .dev-frame-btn {
67
+ background: none;
68
+ border: none;
69
+ color: #793205;
70
+ cursor: pointer;
71
+ font-size: 14px;
72
+ font-family: inherit;
73
+ }
74
+
75
+ .dev-frame-btn:hover {
76
+ text-decoration: underline;
77
+ }
78
+
79
+ .dev-frame-content {
80
+ position: relative;
81
+ flex: 1;
82
+ min-height: 0;
83
+ }
84
+
85
+ .dev-frame iframe {
86
+ position: absolute;
87
+ top: 0;
88
+ left: 0;
89
+ width: 100%;
90
+ height: 100%;
91
+ border: none;
92
+ }
93
+
94
+ .dev-frame-error .dev-frame-header {
95
+ background: #fee2e2;
96
+ color: #991b1b;
97
+ }
98
+
99
+ .dev-frame-warning {
100
+ flex: 1;
101
+ display: flex;
102
+ flex-direction: column;
103
+ align-items: center;
104
+ justify-content: center;
105
+ padding: 20px;
106
+ background: #fef3c7;
107
+ color: #92400e;
108
+ text-align: center;
109
+ }
110
+
111
+ .dev-frame-warning-icon {
112
+ font-size: 48px;
113
+ margin-bottom: 12px;
114
+ }
115
+
116
+ .dev-frame-warning-title {
117
+ font-size: 18px;
118
+ font-weight: 600;
119
+ margin-bottom: 8px;
120
+ color: #b45309;
121
+ }
122
+
123
+ .dev-frame-warning-message {
124
+ font-size: 14px;
125
+ line-height: 1.5;
126
+ color: #92400e;
127
+ }
128
+
129
+ .dev-frame-error-overlay {
130
+ position: absolute;
131
+ top: 0;
132
+ left: 0;
133
+ width: 100%;
134
+ height: 100%;
135
+ display: flex;
136
+ flex-direction: column;
137
+ align-items: center;
138
+ justify-content: center;
139
+ padding: 20px;
140
+ background: linear-gradient(135deg, #2d1f1f 0%, #1f1a1a 100%);
141
+ z-index: 15;
142
+ color: #fca5a5;
143
+ text-align: center;
144
+ overflow: auto;
145
+ }
146
+
147
+ .dev-frame-error-icon {
148
+ font-size: 36px;
149
+ margin-bottom: 12px;
150
+ }
151
+
152
+ .dev-frame-error-title {
153
+ font-size: 18px;
154
+ font-weight: 600;
155
+ margin-bottom: 12px;
156
+ color: #f87171;
157
+ }
158
+
159
+ .dev-frame-error-message {
160
+ font-size: 12px;
161
+ line-height: 1.5;
162
+ color: #fca5a5;
163
+ background: rgba(0, 0, 0, 0.3);
164
+ padding: 12px;
165
+ border-radius: 8px;
166
+ max-width: 100%;
167
+ max-height: 200px;
168
+ overflow: auto;
169
+ white-space: pre-wrap;
170
+ word-break: break-word;
171
+ text-align: left;
172
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
173
+ }
174
+
175
+ .dev-frame-error-btn {
176
+ margin-top: 16px;
177
+ padding: 8px 16px;
178
+ background: #ef4444;
179
+ color: white;
180
+ border: none;
181
+ border-radius: 6px;
182
+ cursor: pointer;
183
+ font-size: 14px;
184
+ font-family: inherit;
185
+ transition: background 0.2s;
186
+ }
187
+
188
+ .dev-frame-error-btn:hover {
189
+ background: #dc2626;
190
+ }
191
+ `;
@@ -0,0 +1,56 @@
1
+ export type I18nResources = {
2
+ [lng: string]: {
3
+ [ns: string]: Record<string, unknown>;
4
+ };
5
+ };
6
+ /**
7
+ * i18n metadata for a build.
8
+ */
9
+ export interface I18nMeta {
10
+ path: string;
11
+ primaryLng: string;
12
+ namespaces: string[];
13
+ languages: string[];
14
+ }
15
+ export interface DevAppInfo {
16
+ appId: string;
17
+ apiKey: string;
18
+ endpoint: string;
19
+ buildUrl?: string;
20
+ }
21
+ /**
22
+ * Load i18n resources from a folder path.
23
+ * Expected structure: `{i18nPath}/{lng}/{namespace}.json`
24
+ */
25
+ export declare function loadI18nFromPath(i18nPath: string): Promise<I18nResources>;
26
+ /**
27
+ * Scan an i18n folder and return i18n metadata.
28
+ * Expected structure: `{i18nPath}/{lng}/{namespace}.json`
29
+ *
30
+ * @param i18nPath - Path to i18n folder relative to cwd
31
+ * @param primaryLng - Source language code for AI translations (default: 'en')
32
+ * @returns i18n metadata with path, primaryLng, namespaces, and languages
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * const i18n = await getI18nMeta('./src/i18n', 'en');
37
+ * // Returns: {
38
+ * // path: 'km-i18n',
39
+ * // primaryLng: 'en',
40
+ * // namespaces: ['ui', 'game', 'setup'],
41
+ * // languages: ['en', 'et']
42
+ * // }
43
+ * ```
44
+ */
45
+ export declare function getI18nMeta(i18nPath: string, primaryLng?: string): Promise<I18nMeta | null>;
46
+ /**
47
+ * Sync primary language i18n files to the dev app build directory (initial sync).
48
+ * Only uploads primaryLng since that's what AI translations use as source.
49
+ * Other local languages are served by kit's dev middleware.
50
+ */
51
+ export declare function syncAllI18nToDevApp(devAppInfo: DevAppInfo, i18nResources: I18nResources, primaryLng?: string): Promise<void>;
52
+ /**
53
+ * Sync a single i18n file to the dev app build directory.
54
+ * Only syncs if the file is for the primary language.
55
+ */
56
+ export declare function syncI18nFile(devAppInfo: DevAppInfo, i18nPath: string, changedFilePath: string, primaryLng?: string): Promise<void>;
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadI18nFromPath = loadI18nFromPath;
7
+ exports.getI18nMeta = getI18nMeta;
8
+ exports.syncAllI18nToDevApp = syncAllI18nToDevApp;
9
+ exports.syncI18nFile = syncI18nFile;
10
+ const promises_1 = __importDefault(require("fs/promises"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const dev_app_1 = require("./dev-app");
13
+ /**
14
+ * Load i18n resources from a folder path.
15
+ * Expected structure: `{i18nPath}/{lng}/{namespace}.json`
16
+ */
17
+ async function loadI18nFromPath(i18nPath) {
18
+ const resources = {};
19
+ const absolutePath = path_1.default.resolve(process.cwd(), i18nPath);
20
+ try {
21
+ const entries = await promises_1.default.readdir(absolutePath, { withFileTypes: true });
22
+ const languageDirs = entries.filter((e) => e.isDirectory());
23
+ for (const langDir of languageDirs) {
24
+ const lng = langDir.name;
25
+ const langPath = path_1.default.join(absolutePath, lng);
26
+ const files = await promises_1.default.readdir(langPath);
27
+ const jsonFiles = files.filter((f) => f.endsWith(".json"));
28
+ if (jsonFiles.length > 0) {
29
+ resources[lng] = {};
30
+ for (const jsonFile of jsonFiles) {
31
+ const ns = jsonFile.replace(".json", "");
32
+ const filePath = path_1.default.join(langPath, jsonFile);
33
+ const content = await promises_1.default.readFile(filePath, "utf8");
34
+ resources[lng][ns] = JSON.parse(content);
35
+ }
36
+ }
37
+ }
38
+ }
39
+ catch (error) {
40
+ console.error(`[kokimoki-kit] Failed to load i18n from path "${i18nPath}":`, error);
41
+ }
42
+ return resources;
43
+ }
44
+ /**
45
+ * Scan an i18n folder and return i18n metadata.
46
+ * Expected structure: `{i18nPath}/{lng}/{namespace}.json`
47
+ *
48
+ * @param i18nPath - Path to i18n folder relative to cwd
49
+ * @param primaryLng - Source language code for AI translations (default: 'en')
50
+ * @returns i18n metadata with path, primaryLng, namespaces, and languages
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * const i18n = await getI18nMeta('./src/i18n', 'en');
55
+ * // Returns: {
56
+ * // path: 'km-i18n',
57
+ * // primaryLng: 'en',
58
+ * // namespaces: ['ui', 'game', 'setup'],
59
+ * // languages: ['en', 'et']
60
+ * // }
61
+ * ```
62
+ */
63
+ async function getI18nMeta(i18nPath, primaryLng = "en") {
64
+ const absolutePath = path_1.default.resolve(process.cwd(), i18nPath);
65
+ const languages = [];
66
+ const namespacesSet = new Set();
67
+ try {
68
+ const entries = await promises_1.default.readdir(absolutePath, { withFileTypes: true });
69
+ const languageDirs = entries.filter((e) => e.isDirectory());
70
+ for (const langDir of languageDirs) {
71
+ const lng = langDir.name;
72
+ const langPath = path_1.default.join(absolutePath, lng);
73
+ const dirFiles = await promises_1.default.readdir(langPath);
74
+ const jsonFiles = dirFiles.filter((f) => f.endsWith(".json"));
75
+ if (jsonFiles.length > 0) {
76
+ languages.push(lng);
77
+ for (const jsonFile of jsonFiles) {
78
+ namespacesSet.add(jsonFile.replace(".json", ""));
79
+ }
80
+ }
81
+ }
82
+ }
83
+ catch (error) {
84
+ console.error(`[kokimoki-kit] Failed to scan i18n path "${i18nPath}":`, error);
85
+ return null;
86
+ }
87
+ if (languages.length === 0) {
88
+ return null;
89
+ }
90
+ return {
91
+ path: "km-i18n",
92
+ primaryLng,
93
+ namespaces: Array.from(namespacesSet),
94
+ languages,
95
+ };
96
+ }
97
+ /**
98
+ * Sync primary language i18n files to the dev app build directory (initial sync).
99
+ * Only uploads primaryLng since that's what AI translations use as source.
100
+ * Other local languages are served by kit's dev middleware.
101
+ */
102
+ async function syncAllI18nToDevApp(devAppInfo, i18nResources, primaryLng = "en") {
103
+ const primaryNamespaces = i18nResources[primaryLng];
104
+ if (!primaryNamespaces || Object.keys(primaryNamespaces).length === 0) {
105
+ return;
106
+ }
107
+ const { appId, apiKey, endpoint } = devAppInfo;
108
+ const syncedFiles = [];
109
+ // Collect namespaces from primary language only
110
+ const namespaces = Object.keys(primaryNamespaces);
111
+ // Set i18n metadata first
112
+ try {
113
+ await (0, dev_app_1.setDevAppI18n)(endpoint, apiKey, appId, {
114
+ path: "km-i18n",
115
+ primaryLng,
116
+ namespaces,
117
+ });
118
+ console.warn(`[kokimoki-kit] Set i18n metadata: ${namespaces.join(", ")}`);
119
+ }
120
+ catch (e) {
121
+ console.warn(`[kokimoki-kit] Failed to set i18n metadata:`, e instanceof Error ? e.message : e);
122
+ return;
123
+ }
124
+ // Upload primary language translations (used as source for AI translations)
125
+ for (const [ns, translations] of Object.entries(primaryNamespaces)) {
126
+ try {
127
+ await (0, dev_app_1.setDevAppTranslation)(endpoint, apiKey, appId, primaryLng, ns, translations);
128
+ syncedFiles.push(`${primaryLng}/${ns}`);
129
+ }
130
+ catch (e) {
131
+ console.warn(`[kokimoki-kit] Failed to sync translation ${primaryLng}/${ns}:`, e instanceof Error ? e.message : e);
132
+ }
133
+ }
134
+ if (syncedFiles.length > 0) {
135
+ console.warn(`[kokimoki-kit] Synced i18n: ${syncedFiles.join(", ")}`);
136
+ }
137
+ }
138
+ /**
139
+ * Sync a single i18n file to the dev app build directory.
140
+ * Only syncs if the file is for the primary language.
141
+ */
142
+ async function syncI18nFile(devAppInfo, i18nPath, changedFilePath, primaryLng = "en") {
143
+ const i18nAbsolutePath = path_1.default.resolve(process.cwd(), i18nPath);
144
+ const relativePath = path_1.default.relative(i18nAbsolutePath, changedFilePath);
145
+ const parts = relativePath.split(path_1.default.sep);
146
+ if (parts.length !== 2)
147
+ return;
148
+ const [lng, filename] = parts;
149
+ const ns = filename.replace(".json", "");
150
+ // Only sync primary language (used as source for AI translations)
151
+ if (lng !== primaryLng) {
152
+ return;
153
+ }
154
+ const { appId, apiKey, endpoint } = devAppInfo;
155
+ try {
156
+ const content = await promises_1.default.readFile(changedFilePath, "utf8");
157
+ const translations = JSON.parse(content);
158
+ await (0, dev_app_1.setDevAppTranslation)(endpoint, apiKey, appId, lng, ns, translations);
159
+ console.warn(`[kokimoki-kit] Synced i18n: ${lng}/${ns}`);
160
+ }
161
+ catch (e) {
162
+ console.warn(`[kokimoki-kit] Failed to sync i18n ${lng}/${ns}:`, e instanceof Error ? e.message : e);
163
+ }
164
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Development overlay HTML templates for kokimoki-kit plugin
3
+ */
4
+ export interface DevAppError {
5
+ code: string;
6
+ message: string;
7
+ }
8
+ /**
9
+ * Generate a full loading page HTML that polls for initialization completion
10
+ */
11
+ export declare function renderLoadingPage(): string;
12
+ /**
13
+ * Generate error page HTML for dev app errors (full page)
14
+ */
15
+ export declare function renderErrorPage(error: DevAppError): string;
16
+ /**
17
+ * Generate stores changed warning page HTML (full page, not overlay)
18
+ */
19
+ export declare function renderStoresChangedPage(canReset: boolean): string;