@contentstorage/i18next-plugin 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +453 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +384 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +391 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +73 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/post-processor.d.ts +42 -0
- package/dist/post-processor.d.ts.map +1 -0
- package/dist/types.d.ts +94 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils.d.ts +88 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if the code is running in a browser environment
|
|
3
|
+
*/
|
|
4
|
+
function isBrowser() {
|
|
5
|
+
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Gets the ContentStorage window object with type safety
|
|
9
|
+
*/
|
|
10
|
+
function getContentStorageWindow() {
|
|
11
|
+
if (!isBrowser())
|
|
12
|
+
return null;
|
|
13
|
+
return window;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Detects if the application is running in ContentStorage live editor mode
|
|
17
|
+
*
|
|
18
|
+
* @param liveEditorParam - Query parameter name to check
|
|
19
|
+
* @param forceLiveMode - Force live mode regardless of environment
|
|
20
|
+
* @returns true if in live editor mode
|
|
21
|
+
*/
|
|
22
|
+
function detectLiveEditorMode(liveEditorParam = 'contentstorage_live_editor', forceLiveMode = false) {
|
|
23
|
+
if (forceLiveMode)
|
|
24
|
+
return true;
|
|
25
|
+
if (!isBrowser())
|
|
26
|
+
return false;
|
|
27
|
+
try {
|
|
28
|
+
const win = getContentStorageWindow();
|
|
29
|
+
if (!win)
|
|
30
|
+
return false;
|
|
31
|
+
// Check 1: Running in an iframe
|
|
32
|
+
const inIframe = win.self !== win.top;
|
|
33
|
+
// Check 2: URL has the live editor marker
|
|
34
|
+
const urlParams = new URLSearchParams(win.location.search);
|
|
35
|
+
const hasMarker = urlParams.has(liveEditorParam);
|
|
36
|
+
return !!(inIframe && hasMarker);
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
// Cross-origin restrictions might block window.top access
|
|
40
|
+
// This is expected when not in live editor mode
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Initializes the global memory map if it doesn't exist
|
|
46
|
+
*/
|
|
47
|
+
function initializeMemoryMap() {
|
|
48
|
+
const win = getContentStorageWindow();
|
|
49
|
+
if (!win)
|
|
50
|
+
return null;
|
|
51
|
+
if (!win.memoryMap) {
|
|
52
|
+
win.memoryMap = new Map();
|
|
53
|
+
}
|
|
54
|
+
return win.memoryMap;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Gets the global memory map
|
|
58
|
+
*/
|
|
59
|
+
function getMemoryMap() {
|
|
60
|
+
const win = getContentStorageWindow();
|
|
61
|
+
return (win === null || win === void 0 ? void 0 : win.memoryMap) || null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Normalizes i18next key format to consistent dot notation
|
|
65
|
+
* Converts namespace:key format to namespace.key
|
|
66
|
+
*
|
|
67
|
+
* @param key - The translation key
|
|
68
|
+
* @param namespace - Optional namespace
|
|
69
|
+
* @returns Normalized key in dot notation
|
|
70
|
+
*/
|
|
71
|
+
function normalizeKey(key, namespace) {
|
|
72
|
+
let normalizedKey = key;
|
|
73
|
+
// Convert colon notation to dot notation
|
|
74
|
+
if (normalizedKey.includes(':')) {
|
|
75
|
+
normalizedKey = normalizedKey.replace(':', '.');
|
|
76
|
+
}
|
|
77
|
+
// Prepend namespace if provided and not already in key
|
|
78
|
+
if (namespace && !normalizedKey.startsWith(`${namespace}.`)) {
|
|
79
|
+
normalizedKey = `${namespace}.${normalizedKey}`;
|
|
80
|
+
}
|
|
81
|
+
return normalizedKey;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Tracks a translation in the memory map
|
|
85
|
+
*
|
|
86
|
+
* @param translationValue - The actual translated text
|
|
87
|
+
* @param translationKey - The content ID (i18next key)
|
|
88
|
+
* @param namespace - Optional namespace
|
|
89
|
+
* @param language - Optional language code
|
|
90
|
+
* @param debug - Enable debug logging
|
|
91
|
+
*/
|
|
92
|
+
function trackTranslation(translationValue, translationKey, namespace, language, debug = false) {
|
|
93
|
+
const memoryMap = getMemoryMap();
|
|
94
|
+
if (!memoryMap)
|
|
95
|
+
return;
|
|
96
|
+
// Normalize the key
|
|
97
|
+
const normalizedKey = normalizeKey(translationKey, namespace);
|
|
98
|
+
// Get or create entry
|
|
99
|
+
const existingEntry = memoryMap.get(translationValue);
|
|
100
|
+
const idSet = existingEntry ? existingEntry.ids : new Set();
|
|
101
|
+
idSet.add(normalizedKey);
|
|
102
|
+
const entry = {
|
|
103
|
+
ids: idSet,
|
|
104
|
+
type: 'text',
|
|
105
|
+
metadata: {
|
|
106
|
+
namespace,
|
|
107
|
+
language,
|
|
108
|
+
trackedAt: Date.now(),
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
memoryMap.set(translationValue, entry);
|
|
112
|
+
if (debug) {
|
|
113
|
+
console.log('[ContentStorage] Tracked translation:', {
|
|
114
|
+
value: translationValue,
|
|
115
|
+
key: normalizedKey,
|
|
116
|
+
namespace,
|
|
117
|
+
language,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Cleans up old entries from memory map when size exceeds limit
|
|
123
|
+
* Removes oldest entries first (based on trackedAt timestamp)
|
|
124
|
+
*
|
|
125
|
+
* @param maxSize - Maximum number of entries to keep
|
|
126
|
+
*/
|
|
127
|
+
function cleanupMemoryMap(maxSize) {
|
|
128
|
+
const memoryMap = getMemoryMap();
|
|
129
|
+
if (!memoryMap || memoryMap.size <= maxSize)
|
|
130
|
+
return;
|
|
131
|
+
// Convert to array with timestamps
|
|
132
|
+
const entries = Array.from(memoryMap.entries()).map(([key, value]) => {
|
|
133
|
+
var _a;
|
|
134
|
+
return ({
|
|
135
|
+
key,
|
|
136
|
+
value,
|
|
137
|
+
timestamp: ((_a = value.metadata) === null || _a === void 0 ? void 0 : _a.trackedAt) || 0,
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
// Sort by timestamp (oldest first)
|
|
141
|
+
entries.sort((a, b) => a.timestamp - b.timestamp);
|
|
142
|
+
// Calculate how many to remove
|
|
143
|
+
const toRemove = memoryMap.size - maxSize;
|
|
144
|
+
// Remove oldest entries
|
|
145
|
+
for (let i = 0; i < toRemove; i++) {
|
|
146
|
+
memoryMap.delete(entries[i].key);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Deeply traverses a translation object and extracts all string values with their keys
|
|
151
|
+
*
|
|
152
|
+
* @param obj - Translation object to traverse
|
|
153
|
+
* @param prefix - Current key prefix (for nested objects)
|
|
154
|
+
* @returns Array of [key, value] pairs
|
|
155
|
+
*/
|
|
156
|
+
function flattenTranslations(obj, prefix = '') {
|
|
157
|
+
const results = [];
|
|
158
|
+
for (const key in obj) {
|
|
159
|
+
if (!Object.prototype.hasOwnProperty.call(obj, key))
|
|
160
|
+
continue;
|
|
161
|
+
const value = obj[key];
|
|
162
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
163
|
+
if (typeof value === 'string') {
|
|
164
|
+
results.push([fullKey, value]);
|
|
165
|
+
}
|
|
166
|
+
else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
167
|
+
// Recurse into nested objects
|
|
168
|
+
results.push(...flattenTranslations(value, fullKey));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return results;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Debug helper to log memory map contents
|
|
175
|
+
*/
|
|
176
|
+
function debugMemoryMap() {
|
|
177
|
+
const memoryMap = getMemoryMap();
|
|
178
|
+
if (!memoryMap) {
|
|
179
|
+
console.log('[ContentStorage] Memory map not initialized');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
console.log('[ContentStorage] Memory map contents:');
|
|
183
|
+
console.log(`Total entries: ${memoryMap.size}`);
|
|
184
|
+
const entries = Array.from(memoryMap.entries()).slice(0, 10);
|
|
185
|
+
console.table(entries.map(([value, entry]) => {
|
|
186
|
+
var _a;
|
|
187
|
+
return ({
|
|
188
|
+
value: value.substring(0, 50),
|
|
189
|
+
keys: Array.from(entry.ids).join(', '),
|
|
190
|
+
namespace: ((_a = entry.metadata) === null || _a === void 0 ? void 0 : _a.namespace) || 'N/A',
|
|
191
|
+
});
|
|
192
|
+
}));
|
|
193
|
+
if (memoryMap.size > 10) {
|
|
194
|
+
console.log(`... and ${memoryMap.size - 10} more entries`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* ContentStorage i18next Backend Plugin
|
|
200
|
+
*
|
|
201
|
+
* This plugin enables translation tracking for the ContentStorage live editor
|
|
202
|
+
* by maintaining a memory map of translations and their keys.
|
|
203
|
+
*
|
|
204
|
+
* Features:
|
|
205
|
+
* - Automatic live editor mode detection
|
|
206
|
+
* - Translation tracking with memory map
|
|
207
|
+
* - Support for nested translations
|
|
208
|
+
* - Memory management with size limits
|
|
209
|
+
* - Custom CDN or load path support
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```typescript
|
|
213
|
+
* import i18next from 'i18next';
|
|
214
|
+
* import ContentStorageBackend from '@contentstorage/i18next-plugin';
|
|
215
|
+
*
|
|
216
|
+
* i18next
|
|
217
|
+
* .use(ContentStorageBackend)
|
|
218
|
+
* .init({
|
|
219
|
+
* backend: {
|
|
220
|
+
* contentKey: 'your-content-key',
|
|
221
|
+
* debug: true
|
|
222
|
+
* }
|
|
223
|
+
* });
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
class ContentStorageBackend {
|
|
227
|
+
constructor(_services, options, _i18nextOptions) {
|
|
228
|
+
this.type = 'backend';
|
|
229
|
+
this.isLiveMode = false;
|
|
230
|
+
this.options = options || {};
|
|
231
|
+
// Initialize if services and i18nextOptions are provided
|
|
232
|
+
// This allows i18next to initialize the plugin automatically
|
|
233
|
+
if (_services && _i18nextOptions) {
|
|
234
|
+
this.init(_services, options, _i18nextOptions);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Initialize the plugin
|
|
239
|
+
* Called by i18next during initialization
|
|
240
|
+
*/
|
|
241
|
+
init(services, backendOptions = {}, i18nextOptions = {}) {
|
|
242
|
+
this.options = {
|
|
243
|
+
cdnBaseUrl: 'https://cdn.contentstorage.app',
|
|
244
|
+
debug: false,
|
|
245
|
+
maxMemoryMapSize: 10000,
|
|
246
|
+
liveEditorParam: 'contentstorage_live_editor',
|
|
247
|
+
forceLiveMode: false,
|
|
248
|
+
...backendOptions,
|
|
249
|
+
};
|
|
250
|
+
// Detect live editor mode
|
|
251
|
+
this.isLiveMode = detectLiveEditorMode(this.options.liveEditorParam, this.options.forceLiveMode);
|
|
252
|
+
if (this.isLiveMode) {
|
|
253
|
+
// Initialize memory map
|
|
254
|
+
initializeMemoryMap();
|
|
255
|
+
if (this.options.debug) {
|
|
256
|
+
console.log('[ContentStorage] Live editor mode enabled');
|
|
257
|
+
console.log('[ContentStorage] Plugin initialized with options:', this.options);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
else if (this.options.debug) {
|
|
261
|
+
console.log('[ContentStorage] Running in normal mode (not live editor)');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Read translations for a given language and namespace
|
|
266
|
+
* This is the main method called by i18next to load translations
|
|
267
|
+
*/
|
|
268
|
+
read(language, namespace, callback) {
|
|
269
|
+
if (this.options.debug) {
|
|
270
|
+
console.log(`[ContentStorage] Loading translations: ${language}/${namespace}`);
|
|
271
|
+
}
|
|
272
|
+
this.loadTranslations(language, namespace)
|
|
273
|
+
.then((translations) => {
|
|
274
|
+
// Track translations if in live mode
|
|
275
|
+
if (this.isLiveMode && this.shouldTrackNamespace(namespace)) {
|
|
276
|
+
this.trackTranslations(translations, namespace, language);
|
|
277
|
+
// Cleanup if needed
|
|
278
|
+
if (this.options.maxMemoryMapSize) {
|
|
279
|
+
cleanupMemoryMap(this.options.maxMemoryMapSize);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
callback(null, translations);
|
|
283
|
+
})
|
|
284
|
+
.catch((error) => {
|
|
285
|
+
if (this.options.debug) {
|
|
286
|
+
console.error('[ContentStorage] Failed to load translations:', error);
|
|
287
|
+
}
|
|
288
|
+
callback(error, false);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Load translations from CDN or custom source
|
|
293
|
+
*/
|
|
294
|
+
async loadTranslations(language, namespace) {
|
|
295
|
+
const url = this.getLoadPath(language, namespace);
|
|
296
|
+
if (this.options.debug) {
|
|
297
|
+
console.log(`[ContentStorage] Fetching from: ${url}`);
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
const fetchFn = this.options.request || this.defaultFetch.bind(this);
|
|
301
|
+
return await fetchFn(url, {
|
|
302
|
+
method: 'GET',
|
|
303
|
+
headers: {
|
|
304
|
+
'Accept': 'application/json',
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
if (this.options.debug) {
|
|
310
|
+
console.error('[ContentStorage] Fetch error:', error);
|
|
311
|
+
}
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Default fetch implementation
|
|
317
|
+
*/
|
|
318
|
+
async defaultFetch(url, options) {
|
|
319
|
+
const response = await fetch(url, options);
|
|
320
|
+
if (!response.ok) {
|
|
321
|
+
throw new Error(`Failed to load translations: ${response.status} ${response.statusText}`);
|
|
322
|
+
}
|
|
323
|
+
return response.json();
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Get the URL to load translations from
|
|
327
|
+
*/
|
|
328
|
+
getLoadPath(language, namespace) {
|
|
329
|
+
const { loadPath, cdnBaseUrl, contentKey } = this.options;
|
|
330
|
+
// Custom load path function
|
|
331
|
+
if (typeof loadPath === 'function') {
|
|
332
|
+
return loadPath(language, namespace);
|
|
333
|
+
}
|
|
334
|
+
// Custom load path string with interpolation
|
|
335
|
+
if (typeof loadPath === 'string') {
|
|
336
|
+
return loadPath
|
|
337
|
+
.replace('{{lng}}', language)
|
|
338
|
+
.replace('{{ns}}', namespace);
|
|
339
|
+
}
|
|
340
|
+
// Default CDN path
|
|
341
|
+
if (!contentKey) {
|
|
342
|
+
throw new Error('[ContentStorage] contentKey is required when using default CDN path');
|
|
343
|
+
}
|
|
344
|
+
return `${cdnBaseUrl}/${contentKey}/content/${language}/${namespace}.json`;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Check if a namespace should be tracked
|
|
348
|
+
*/
|
|
349
|
+
shouldTrackNamespace(namespace) {
|
|
350
|
+
const { trackNamespaces } = this.options;
|
|
351
|
+
// If no filter specified, track all namespaces
|
|
352
|
+
if (!trackNamespaces || trackNamespaces.length === 0) {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
return trackNamespaces.includes(namespace);
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Track all translations in the loaded data
|
|
359
|
+
*/
|
|
360
|
+
trackTranslations(translations, namespace, language) {
|
|
361
|
+
if (!isBrowser())
|
|
362
|
+
return;
|
|
363
|
+
const flatTranslations = flattenTranslations(translations);
|
|
364
|
+
for (const [key, value] of flatTranslations) {
|
|
365
|
+
// Skip empty values
|
|
366
|
+
if (!value)
|
|
367
|
+
continue;
|
|
368
|
+
trackTranslation(value, key, namespace, language, this.options.debug);
|
|
369
|
+
}
|
|
370
|
+
if (this.options.debug) {
|
|
371
|
+
console.log(`[ContentStorage] Tracked ${flatTranslations.length} translations for ${namespace}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
ContentStorageBackend.type = 'backend';
|
|
376
|
+
/**
|
|
377
|
+
* Create a new instance of the ContentStorage backend
|
|
378
|
+
*/
|
|
379
|
+
function createContentStorageBackend(options) {
|
|
380
|
+
return new ContentStorageBackend(undefined, options);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export { ContentStorageBackend, createContentStorageBackend, debugMemoryMap, ContentStorageBackend as default };
|
|
384
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/utils.ts","../src/plugin.ts"],"sourcesContent":["import type { ContentStorageWindow, MemoryMap, MemoryMapEntry } from './types';\n\n/**\n * Checks if the code is running in a browser environment\n */\nexport function isBrowser(): boolean {\n return typeof window !== 'undefined' && typeof document !== 'undefined';\n}\n\n/**\n * Gets the ContentStorage window object with type safety\n */\nexport function getContentStorageWindow(): ContentStorageWindow | null {\n if (!isBrowser()) return null;\n return window as ContentStorageWindow;\n}\n\n/**\n * Detects if the application is running in ContentStorage live editor mode\n *\n * @param liveEditorParam - Query parameter name to check\n * @param forceLiveMode - Force live mode regardless of environment\n * @returns true if in live editor mode\n */\nexport function detectLiveEditorMode(\n liveEditorParam: string = 'contentstorage_live_editor',\n forceLiveMode: boolean = false\n): boolean {\n if (forceLiveMode) return true;\n if (!isBrowser()) return false;\n\n try {\n const win = getContentStorageWindow();\n if (!win) return false;\n\n // Check 1: Running in an iframe\n const inIframe = win.self !== win.top;\n\n // Check 2: URL has the live editor marker\n const urlParams = new URLSearchParams(win.location.search);\n const hasMarker = urlParams.has(liveEditorParam);\n\n return !!(inIframe && hasMarker);\n } catch (e) {\n // Cross-origin restrictions might block window.top access\n // This is expected when not in live editor mode\n return false;\n }\n}\n\n/**\n * Initializes the global memory map if it doesn't exist\n */\nexport function initializeMemoryMap(): MemoryMap | null {\n const win = getContentStorageWindow();\n if (!win) return null;\n\n if (!win.memoryMap) {\n win.memoryMap = new Map<string, MemoryMapEntry>();\n }\n\n return win.memoryMap;\n}\n\n/**\n * Gets the global memory map\n */\nexport function getMemoryMap(): MemoryMap | null {\n const win = getContentStorageWindow();\n return win?.memoryMap || null;\n}\n\n/**\n * Normalizes i18next key format to consistent dot notation\n * Converts namespace:key format to namespace.key\n *\n * @param key - The translation key\n * @param namespace - Optional namespace\n * @returns Normalized key in dot notation\n */\nexport function normalizeKey(key: string, namespace?: string): string {\n let normalizedKey = key;\n\n // Convert colon notation to dot notation\n if (normalizedKey.includes(':')) {\n normalizedKey = normalizedKey.replace(':', '.');\n }\n\n // Prepend namespace if provided and not already in key\n if (namespace && !normalizedKey.startsWith(`${namespace}.`)) {\n normalizedKey = `${namespace}.${normalizedKey}`;\n }\n\n return normalizedKey;\n}\n\n/**\n * Extracts the base translation key without interpolation context\n * Handles plural forms, contexts, and other i18next features\n *\n * Examples:\n * - 'welcome' -> 'welcome'\n * - 'items_plural' -> 'items'\n * - 'friend_male' -> 'friend'\n *\n * @param key - The translation key\n * @returns Base key without suffixes\n */\nexport function extractBaseKey(key: string): string {\n // Remove plural suffixes (_zero, _one, _two, _few, _many, _other, _plural)\n let baseKey = key.replace(/_(zero|one|two|few|many|other|plural)$/, '');\n\n // Remove context suffixes (anything after last underscore that's not a nested key)\n // Be careful not to remove underscores that are part of the actual key\n // This is a heuristic - contexts usually come at the end\n const lastUnderscore = baseKey.lastIndexOf('_');\n if (lastUnderscore > 0) {\n // Only remove if it looks like a context (short suffix, typically lowercase)\n const suffix = baseKey.substring(lastUnderscore + 1);\n if (suffix.length < 10 && suffix.toLowerCase() === suffix) {\n // This might be a context, but we'll keep it for now to avoid false positives\n // Real context handling should be done at a higher level\n }\n }\n\n return baseKey;\n}\n\n/**\n * Removes interpolation variables from a translated string\n *\n * Examples:\n * - 'Hello {{name}}!' -> 'Hello !'\n * - 'You have {{count}} items' -> 'You have items'\n *\n * @param value - The translated string\n * @returns String with interpolations removed\n */\nexport function removeInterpolation(value: string): string {\n // Remove i18next interpolation syntax: {{variable}}\n return value.replace(/\\{\\{[^}]+\\}\\}/g, '').trim();\n}\n\n/**\n * Tracks a translation in the memory map\n *\n * @param translationValue - The actual translated text\n * @param translationKey - The content ID (i18next key)\n * @param namespace - Optional namespace\n * @param language - Optional language code\n * @param debug - Enable debug logging\n */\nexport function trackTranslation(\n translationValue: string,\n translationKey: string,\n namespace?: string,\n language?: string,\n debug: boolean = false\n): void {\n const memoryMap = getMemoryMap();\n if (!memoryMap) return;\n\n // Normalize the key\n const normalizedKey = normalizeKey(translationKey, namespace);\n\n // Get or create entry\n const existingEntry = memoryMap.get(translationValue);\n const idSet = existingEntry ? existingEntry.ids : new Set<string>();\n idSet.add(normalizedKey);\n\n const entry: MemoryMapEntry = {\n ids: idSet,\n type: 'text',\n metadata: {\n namespace,\n language,\n trackedAt: Date.now(),\n },\n };\n\n memoryMap.set(translationValue, entry);\n\n if (debug) {\n console.log('[ContentStorage] Tracked translation:', {\n value: translationValue,\n key: normalizedKey,\n namespace,\n language,\n });\n }\n}\n\n/**\n * Cleans up old entries from memory map when size exceeds limit\n * Removes oldest entries first (based on trackedAt timestamp)\n *\n * @param maxSize - Maximum number of entries to keep\n */\nexport function cleanupMemoryMap(maxSize: number): void {\n const memoryMap = getMemoryMap();\n if (!memoryMap || memoryMap.size <= maxSize) return;\n\n // Convert to array with timestamps\n const entries = Array.from(memoryMap.entries()).map(([key, value]) => ({\n key,\n value,\n timestamp: value.metadata?.trackedAt || 0,\n }));\n\n // Sort by timestamp (oldest first)\n entries.sort((a, b) => a.timestamp - b.timestamp);\n\n // Calculate how many to remove\n const toRemove = memoryMap.size - maxSize;\n\n // Remove oldest entries\n for (let i = 0; i < toRemove; i++) {\n memoryMap.delete(entries[i].key);\n }\n}\n\n/**\n * Deeply traverses a translation object and extracts all string values with their keys\n *\n * @param obj - Translation object to traverse\n * @param prefix - Current key prefix (for nested objects)\n * @returns Array of [key, value] pairs\n */\nexport function flattenTranslations(\n obj: any,\n prefix: string = ''\n): Array<[string, string]> {\n const results: Array<[string, string]> = [];\n\n for (const key in obj) {\n if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;\n\n const value = obj[key];\n const fullKey = prefix ? `${prefix}.${key}` : key;\n\n if (typeof value === 'string') {\n results.push([fullKey, value]);\n } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n // Recurse into nested objects\n results.push(...flattenTranslations(value, fullKey));\n }\n }\n\n return results;\n}\n\n/**\n * Debug helper to log memory map contents\n */\nexport function debugMemoryMap(): void {\n const memoryMap = getMemoryMap();\n if (!memoryMap) {\n console.log('[ContentStorage] Memory map not initialized');\n return;\n }\n\n console.log('[ContentStorage] Memory map contents:');\n console.log(`Total entries: ${memoryMap.size}`);\n\n const entries = Array.from(memoryMap.entries()).slice(0, 10);\n console.table(\n entries.map(([value, entry]) => ({\n value: value.substring(0, 50),\n keys: Array.from(entry.ids).join(', '),\n namespace: entry.metadata?.namespace || 'N/A',\n }))\n );\n\n if (memoryMap.size > 10) {\n console.log(`... and ${memoryMap.size - 10} more entries`);\n }\n}\n","import type {\n BackendModule,\n ReadCallback,\n Services,\n InitOptions,\n} from 'i18next';\nimport type {\n ContentStoragePluginOptions,\n TranslationData,\n} from './types';\nimport {\n detectLiveEditorMode,\n initializeMemoryMap,\n trackTranslation,\n cleanupMemoryMap,\n flattenTranslations,\n isBrowser,\n} from './utils';\n\n/**\n * ContentStorage i18next Backend Plugin\n *\n * This plugin enables translation tracking for the ContentStorage live editor\n * by maintaining a memory map of translations and their keys.\n *\n * Features:\n * - Automatic live editor mode detection\n * - Translation tracking with memory map\n * - Support for nested translations\n * - Memory management with size limits\n * - Custom CDN or load path support\n *\n * @example\n * ```typescript\n * import i18next from 'i18next';\n * import ContentStorageBackend from '@contentstorage/i18next-plugin';\n *\n * i18next\n * .use(ContentStorageBackend)\n * .init({\n * backend: {\n * contentKey: 'your-content-key',\n * debug: true\n * }\n * });\n * ```\n */\nexport class ContentStorageBackend implements BackendModule<ContentStoragePluginOptions> {\n static type: 'backend' = 'backend';\n type: 'backend' = 'backend';\n\n private options: ContentStoragePluginOptions;\n private isLiveMode: boolean = false;\n\n constructor(_services?: Services, options?: ContentStoragePluginOptions, _i18nextOptions?: InitOptions) {\n this.options = options || {};\n\n // Initialize if services and i18nextOptions are provided\n // This allows i18next to initialize the plugin automatically\n if (_services && _i18nextOptions) {\n this.init(_services, options, _i18nextOptions);\n }\n }\n\n /**\n * Initialize the plugin\n * Called by i18next during initialization\n */\n init(\n services: Services,\n backendOptions: ContentStoragePluginOptions = {},\n i18nextOptions: InitOptions = {}\n ): void {\n // Store services and i18nextOptions for potential future use\n // Note: Currently not used but kept in signature for i18next compatibility\n void services;\n void i18nextOptions;\n\n this.options = {\n cdnBaseUrl: 'https://cdn.contentstorage.app',\n debug: false,\n maxMemoryMapSize: 10000,\n liveEditorParam: 'contentstorage_live_editor',\n forceLiveMode: false,\n ...backendOptions,\n };\n\n // Detect live editor mode\n this.isLiveMode = detectLiveEditorMode(\n this.options.liveEditorParam,\n this.options.forceLiveMode\n );\n\n if (this.isLiveMode) {\n // Initialize memory map\n initializeMemoryMap();\n\n if (this.options.debug) {\n console.log('[ContentStorage] Live editor mode enabled');\n console.log('[ContentStorage] Plugin initialized with options:', this.options);\n }\n } else if (this.options.debug) {\n console.log('[ContentStorage] Running in normal mode (not live editor)');\n }\n }\n\n /**\n * Read translations for a given language and namespace\n * This is the main method called by i18next to load translations\n */\n read(\n language: string,\n namespace: string,\n callback: ReadCallback\n ): void {\n if (this.options.debug) {\n console.log(`[ContentStorage] Loading translations: ${language}/${namespace}`);\n }\n\n this.loadTranslations(language, namespace)\n .then((translations) => {\n // Track translations if in live mode\n if (this.isLiveMode && this.shouldTrackNamespace(namespace)) {\n this.trackTranslations(translations, namespace, language);\n\n // Cleanup if needed\n if (this.options.maxMemoryMapSize) {\n cleanupMemoryMap(this.options.maxMemoryMapSize);\n }\n }\n\n callback(null, translations);\n })\n .catch((error) => {\n if (this.options.debug) {\n console.error('[ContentStorage] Failed to load translations:', error);\n }\n callback(error, false);\n });\n }\n\n /**\n * Load translations from CDN or custom source\n */\n private async loadTranslations(\n language: string,\n namespace: string\n ): Promise<TranslationData> {\n const url = this.getLoadPath(language, namespace);\n\n if (this.options.debug) {\n console.log(`[ContentStorage] Fetching from: ${url}`);\n }\n\n try {\n const fetchFn = this.options.request || this.defaultFetch.bind(this);\n return await fetchFn(url, {\n method: 'GET',\n headers: {\n 'Accept': 'application/json',\n },\n });\n } catch (error) {\n if (this.options.debug) {\n console.error('[ContentStorage] Fetch error:', error);\n }\n throw error;\n }\n }\n\n /**\n * Default fetch implementation\n */\n private async defaultFetch(url: string, options: RequestInit): Promise<any> {\n const response = await fetch(url, options);\n\n if (!response.ok) {\n throw new Error(\n `Failed to load translations: ${response.status} ${response.statusText}`\n );\n }\n\n return response.json();\n }\n\n /**\n * Get the URL to load translations from\n */\n private getLoadPath(language: string, namespace: string): string {\n const { loadPath, cdnBaseUrl, contentKey } = this.options;\n\n // Custom load path function\n if (typeof loadPath === 'function') {\n return loadPath(language, namespace);\n }\n\n // Custom load path string with interpolation\n if (typeof loadPath === 'string') {\n return loadPath\n .replace('{{lng}}', language)\n .replace('{{ns}}', namespace);\n }\n\n // Default CDN path\n if (!contentKey) {\n throw new Error(\n '[ContentStorage] contentKey is required when using default CDN path'\n );\n }\n\n return `${cdnBaseUrl}/${contentKey}/content/${language}/${namespace}.json`;\n }\n\n /**\n * Check if a namespace should be tracked\n */\n private shouldTrackNamespace(namespace: string): boolean {\n const { trackNamespaces } = this.options;\n\n // If no filter specified, track all namespaces\n if (!trackNamespaces || trackNamespaces.length === 0) {\n return true;\n }\n\n return trackNamespaces.includes(namespace);\n }\n\n /**\n * Track all translations in the loaded data\n */\n private trackTranslations(\n translations: TranslationData,\n namespace: string,\n language: string\n ): void {\n if (!isBrowser()) return;\n\n const flatTranslations = flattenTranslations(translations);\n\n for (const [key, value] of flatTranslations) {\n // Skip empty values\n if (!value) continue;\n\n trackTranslation(\n value,\n key,\n namespace,\n language,\n this.options.debug\n );\n }\n\n if (this.options.debug) {\n console.log(\n `[ContentStorage] Tracked ${flatTranslations.length} translations for ${namespace}`\n );\n }\n }\n}\n\n/**\n * Create a new instance of the ContentStorage backend\n */\nexport function createContentStorageBackend(\n options?: ContentStoragePluginOptions\n): ContentStorageBackend {\n return new ContentStorageBackend(undefined, options);\n}\n\n// Default export\nexport default ContentStorageBackend;\n"],"names":[],"mappings":"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"}
|