@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/dist/index.js ADDED
@@ -0,0 +1,391 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ /**
6
+ * Checks if the code is running in a browser environment
7
+ */
8
+ function isBrowser() {
9
+ return typeof window !== 'undefined' && typeof document !== 'undefined';
10
+ }
11
+ /**
12
+ * Gets the ContentStorage window object with type safety
13
+ */
14
+ function getContentStorageWindow() {
15
+ if (!isBrowser())
16
+ return null;
17
+ return window;
18
+ }
19
+ /**
20
+ * Detects if the application is running in ContentStorage live editor mode
21
+ *
22
+ * @param liveEditorParam - Query parameter name to check
23
+ * @param forceLiveMode - Force live mode regardless of environment
24
+ * @returns true if in live editor mode
25
+ */
26
+ function detectLiveEditorMode(liveEditorParam = 'contentstorage_live_editor', forceLiveMode = false) {
27
+ if (forceLiveMode)
28
+ return true;
29
+ if (!isBrowser())
30
+ return false;
31
+ try {
32
+ const win = getContentStorageWindow();
33
+ if (!win)
34
+ return false;
35
+ // Check 1: Running in an iframe
36
+ const inIframe = win.self !== win.top;
37
+ // Check 2: URL has the live editor marker
38
+ const urlParams = new URLSearchParams(win.location.search);
39
+ const hasMarker = urlParams.has(liveEditorParam);
40
+ return !!(inIframe && hasMarker);
41
+ }
42
+ catch (e) {
43
+ // Cross-origin restrictions might block window.top access
44
+ // This is expected when not in live editor mode
45
+ return false;
46
+ }
47
+ }
48
+ /**
49
+ * Initializes the global memory map if it doesn't exist
50
+ */
51
+ function initializeMemoryMap() {
52
+ const win = getContentStorageWindow();
53
+ if (!win)
54
+ return null;
55
+ if (!win.memoryMap) {
56
+ win.memoryMap = new Map();
57
+ }
58
+ return win.memoryMap;
59
+ }
60
+ /**
61
+ * Gets the global memory map
62
+ */
63
+ function getMemoryMap() {
64
+ const win = getContentStorageWindow();
65
+ return (win === null || win === void 0 ? void 0 : win.memoryMap) || null;
66
+ }
67
+ /**
68
+ * Normalizes i18next key format to consistent dot notation
69
+ * Converts namespace:key format to namespace.key
70
+ *
71
+ * @param key - The translation key
72
+ * @param namespace - Optional namespace
73
+ * @returns Normalized key in dot notation
74
+ */
75
+ function normalizeKey(key, namespace) {
76
+ let normalizedKey = key;
77
+ // Convert colon notation to dot notation
78
+ if (normalizedKey.includes(':')) {
79
+ normalizedKey = normalizedKey.replace(':', '.');
80
+ }
81
+ // Prepend namespace if provided and not already in key
82
+ if (namespace && !normalizedKey.startsWith(`${namespace}.`)) {
83
+ normalizedKey = `${namespace}.${normalizedKey}`;
84
+ }
85
+ return normalizedKey;
86
+ }
87
+ /**
88
+ * Tracks a translation in the memory map
89
+ *
90
+ * @param translationValue - The actual translated text
91
+ * @param translationKey - The content ID (i18next key)
92
+ * @param namespace - Optional namespace
93
+ * @param language - Optional language code
94
+ * @param debug - Enable debug logging
95
+ */
96
+ function trackTranslation(translationValue, translationKey, namespace, language, debug = false) {
97
+ const memoryMap = getMemoryMap();
98
+ if (!memoryMap)
99
+ return;
100
+ // Normalize the key
101
+ const normalizedKey = normalizeKey(translationKey, namespace);
102
+ // Get or create entry
103
+ const existingEntry = memoryMap.get(translationValue);
104
+ const idSet = existingEntry ? existingEntry.ids : new Set();
105
+ idSet.add(normalizedKey);
106
+ const entry = {
107
+ ids: idSet,
108
+ type: 'text',
109
+ metadata: {
110
+ namespace,
111
+ language,
112
+ trackedAt: Date.now(),
113
+ },
114
+ };
115
+ memoryMap.set(translationValue, entry);
116
+ if (debug) {
117
+ console.log('[ContentStorage] Tracked translation:', {
118
+ value: translationValue,
119
+ key: normalizedKey,
120
+ namespace,
121
+ language,
122
+ });
123
+ }
124
+ }
125
+ /**
126
+ * Cleans up old entries from memory map when size exceeds limit
127
+ * Removes oldest entries first (based on trackedAt timestamp)
128
+ *
129
+ * @param maxSize - Maximum number of entries to keep
130
+ */
131
+ function cleanupMemoryMap(maxSize) {
132
+ const memoryMap = getMemoryMap();
133
+ if (!memoryMap || memoryMap.size <= maxSize)
134
+ return;
135
+ // Convert to array with timestamps
136
+ const entries = Array.from(memoryMap.entries()).map(([key, value]) => {
137
+ var _a;
138
+ return ({
139
+ key,
140
+ value,
141
+ timestamp: ((_a = value.metadata) === null || _a === void 0 ? void 0 : _a.trackedAt) || 0,
142
+ });
143
+ });
144
+ // Sort by timestamp (oldest first)
145
+ entries.sort((a, b) => a.timestamp - b.timestamp);
146
+ // Calculate how many to remove
147
+ const toRemove = memoryMap.size - maxSize;
148
+ // Remove oldest entries
149
+ for (let i = 0; i < toRemove; i++) {
150
+ memoryMap.delete(entries[i].key);
151
+ }
152
+ }
153
+ /**
154
+ * Deeply traverses a translation object and extracts all string values with their keys
155
+ *
156
+ * @param obj - Translation object to traverse
157
+ * @param prefix - Current key prefix (for nested objects)
158
+ * @returns Array of [key, value] pairs
159
+ */
160
+ function flattenTranslations(obj, prefix = '') {
161
+ const results = [];
162
+ for (const key in obj) {
163
+ if (!Object.prototype.hasOwnProperty.call(obj, key))
164
+ continue;
165
+ const value = obj[key];
166
+ const fullKey = prefix ? `${prefix}.${key}` : key;
167
+ if (typeof value === 'string') {
168
+ results.push([fullKey, value]);
169
+ }
170
+ else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
171
+ // Recurse into nested objects
172
+ results.push(...flattenTranslations(value, fullKey));
173
+ }
174
+ }
175
+ return results;
176
+ }
177
+ /**
178
+ * Debug helper to log memory map contents
179
+ */
180
+ function debugMemoryMap() {
181
+ const memoryMap = getMemoryMap();
182
+ if (!memoryMap) {
183
+ console.log('[ContentStorage] Memory map not initialized');
184
+ return;
185
+ }
186
+ console.log('[ContentStorage] Memory map contents:');
187
+ console.log(`Total entries: ${memoryMap.size}`);
188
+ const entries = Array.from(memoryMap.entries()).slice(0, 10);
189
+ console.table(entries.map(([value, entry]) => {
190
+ var _a;
191
+ return ({
192
+ value: value.substring(0, 50),
193
+ keys: Array.from(entry.ids).join(', '),
194
+ namespace: ((_a = entry.metadata) === null || _a === void 0 ? void 0 : _a.namespace) || 'N/A',
195
+ });
196
+ }));
197
+ if (memoryMap.size > 10) {
198
+ console.log(`... and ${memoryMap.size - 10} more entries`);
199
+ }
200
+ }
201
+
202
+ /**
203
+ * ContentStorage i18next Backend Plugin
204
+ *
205
+ * This plugin enables translation tracking for the ContentStorage live editor
206
+ * by maintaining a memory map of translations and their keys.
207
+ *
208
+ * Features:
209
+ * - Automatic live editor mode detection
210
+ * - Translation tracking with memory map
211
+ * - Support for nested translations
212
+ * - Memory management with size limits
213
+ * - Custom CDN or load path support
214
+ *
215
+ * @example
216
+ * ```typescript
217
+ * import i18next from 'i18next';
218
+ * import ContentStorageBackend from '@contentstorage/i18next-plugin';
219
+ *
220
+ * i18next
221
+ * .use(ContentStorageBackend)
222
+ * .init({
223
+ * backend: {
224
+ * contentKey: 'your-content-key',
225
+ * debug: true
226
+ * }
227
+ * });
228
+ * ```
229
+ */
230
+ class ContentStorageBackend {
231
+ constructor(_services, options, _i18nextOptions) {
232
+ this.type = 'backend';
233
+ this.isLiveMode = false;
234
+ this.options = options || {};
235
+ // Initialize if services and i18nextOptions are provided
236
+ // This allows i18next to initialize the plugin automatically
237
+ if (_services && _i18nextOptions) {
238
+ this.init(_services, options, _i18nextOptions);
239
+ }
240
+ }
241
+ /**
242
+ * Initialize the plugin
243
+ * Called by i18next during initialization
244
+ */
245
+ init(services, backendOptions = {}, i18nextOptions = {}) {
246
+ this.options = {
247
+ cdnBaseUrl: 'https://cdn.contentstorage.app',
248
+ debug: false,
249
+ maxMemoryMapSize: 10000,
250
+ liveEditorParam: 'contentstorage_live_editor',
251
+ forceLiveMode: false,
252
+ ...backendOptions,
253
+ };
254
+ // Detect live editor mode
255
+ this.isLiveMode = detectLiveEditorMode(this.options.liveEditorParam, this.options.forceLiveMode);
256
+ if (this.isLiveMode) {
257
+ // Initialize memory map
258
+ initializeMemoryMap();
259
+ if (this.options.debug) {
260
+ console.log('[ContentStorage] Live editor mode enabled');
261
+ console.log('[ContentStorage] Plugin initialized with options:', this.options);
262
+ }
263
+ }
264
+ else if (this.options.debug) {
265
+ console.log('[ContentStorage] Running in normal mode (not live editor)');
266
+ }
267
+ }
268
+ /**
269
+ * Read translations for a given language and namespace
270
+ * This is the main method called by i18next to load translations
271
+ */
272
+ read(language, namespace, callback) {
273
+ if (this.options.debug) {
274
+ console.log(`[ContentStorage] Loading translations: ${language}/${namespace}`);
275
+ }
276
+ this.loadTranslations(language, namespace)
277
+ .then((translations) => {
278
+ // Track translations if in live mode
279
+ if (this.isLiveMode && this.shouldTrackNamespace(namespace)) {
280
+ this.trackTranslations(translations, namespace, language);
281
+ // Cleanup if needed
282
+ if (this.options.maxMemoryMapSize) {
283
+ cleanupMemoryMap(this.options.maxMemoryMapSize);
284
+ }
285
+ }
286
+ callback(null, translations);
287
+ })
288
+ .catch((error) => {
289
+ if (this.options.debug) {
290
+ console.error('[ContentStorage] Failed to load translations:', error);
291
+ }
292
+ callback(error, false);
293
+ });
294
+ }
295
+ /**
296
+ * Load translations from CDN or custom source
297
+ */
298
+ async loadTranslations(language, namespace) {
299
+ const url = this.getLoadPath(language, namespace);
300
+ if (this.options.debug) {
301
+ console.log(`[ContentStorage] Fetching from: ${url}`);
302
+ }
303
+ try {
304
+ const fetchFn = this.options.request || this.defaultFetch.bind(this);
305
+ return await fetchFn(url, {
306
+ method: 'GET',
307
+ headers: {
308
+ 'Accept': 'application/json',
309
+ },
310
+ });
311
+ }
312
+ catch (error) {
313
+ if (this.options.debug) {
314
+ console.error('[ContentStorage] Fetch error:', error);
315
+ }
316
+ throw error;
317
+ }
318
+ }
319
+ /**
320
+ * Default fetch implementation
321
+ */
322
+ async defaultFetch(url, options) {
323
+ const response = await fetch(url, options);
324
+ if (!response.ok) {
325
+ throw new Error(`Failed to load translations: ${response.status} ${response.statusText}`);
326
+ }
327
+ return response.json();
328
+ }
329
+ /**
330
+ * Get the URL to load translations from
331
+ */
332
+ getLoadPath(language, namespace) {
333
+ const { loadPath, cdnBaseUrl, contentKey } = this.options;
334
+ // Custom load path function
335
+ if (typeof loadPath === 'function') {
336
+ return loadPath(language, namespace);
337
+ }
338
+ // Custom load path string with interpolation
339
+ if (typeof loadPath === 'string') {
340
+ return loadPath
341
+ .replace('{{lng}}', language)
342
+ .replace('{{ns}}', namespace);
343
+ }
344
+ // Default CDN path
345
+ if (!contentKey) {
346
+ throw new Error('[ContentStorage] contentKey is required when using default CDN path');
347
+ }
348
+ return `${cdnBaseUrl}/${contentKey}/content/${language}/${namespace}.json`;
349
+ }
350
+ /**
351
+ * Check if a namespace should be tracked
352
+ */
353
+ shouldTrackNamespace(namespace) {
354
+ const { trackNamespaces } = this.options;
355
+ // If no filter specified, track all namespaces
356
+ if (!trackNamespaces || trackNamespaces.length === 0) {
357
+ return true;
358
+ }
359
+ return trackNamespaces.includes(namespace);
360
+ }
361
+ /**
362
+ * Track all translations in the loaded data
363
+ */
364
+ trackTranslations(translations, namespace, language) {
365
+ if (!isBrowser())
366
+ return;
367
+ const flatTranslations = flattenTranslations(translations);
368
+ for (const [key, value] of flatTranslations) {
369
+ // Skip empty values
370
+ if (!value)
371
+ continue;
372
+ trackTranslation(value, key, namespace, language, this.options.debug);
373
+ }
374
+ if (this.options.debug) {
375
+ console.log(`[ContentStorage] Tracked ${flatTranslations.length} translations for ${namespace}`);
376
+ }
377
+ }
378
+ }
379
+ ContentStorageBackend.type = 'backend';
380
+ /**
381
+ * Create a new instance of the ContentStorage backend
382
+ */
383
+ function createContentStorageBackend(options) {
384
+ return new ContentStorageBackend(undefined, options);
385
+ }
386
+
387
+ exports.ContentStorageBackend = ContentStorageBackend;
388
+ exports.createContentStorageBackend = createContentStorageBackend;
389
+ exports.debugMemoryMap = debugMemoryMap;
390
+ exports.default = ContentStorageBackend;
391
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.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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"}
@@ -0,0 +1,73 @@
1
+ import type { BackendModule, ReadCallback, Services, InitOptions } from 'i18next';
2
+ import type { ContentStoragePluginOptions } from './types';
3
+ /**
4
+ * ContentStorage i18next Backend Plugin
5
+ *
6
+ * This plugin enables translation tracking for the ContentStorage live editor
7
+ * by maintaining a memory map of translations and their keys.
8
+ *
9
+ * Features:
10
+ * - Automatic live editor mode detection
11
+ * - Translation tracking with memory map
12
+ * - Support for nested translations
13
+ * - Memory management with size limits
14
+ * - Custom CDN or load path support
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * import i18next from 'i18next';
19
+ * import ContentStorageBackend from '@contentstorage/i18next-plugin';
20
+ *
21
+ * i18next
22
+ * .use(ContentStorageBackend)
23
+ * .init({
24
+ * backend: {
25
+ * contentKey: 'your-content-key',
26
+ * debug: true
27
+ * }
28
+ * });
29
+ * ```
30
+ */
31
+ export declare class ContentStorageBackend implements BackendModule<ContentStoragePluginOptions> {
32
+ static type: 'backend';
33
+ type: 'backend';
34
+ private options;
35
+ private isLiveMode;
36
+ constructor(_services?: Services, options?: ContentStoragePluginOptions, _i18nextOptions?: InitOptions);
37
+ /**
38
+ * Initialize the plugin
39
+ * Called by i18next during initialization
40
+ */
41
+ init(services: Services, backendOptions?: ContentStoragePluginOptions, i18nextOptions?: InitOptions): void;
42
+ /**
43
+ * Read translations for a given language and namespace
44
+ * This is the main method called by i18next to load translations
45
+ */
46
+ read(language: string, namespace: string, callback: ReadCallback): void;
47
+ /**
48
+ * Load translations from CDN or custom source
49
+ */
50
+ private loadTranslations;
51
+ /**
52
+ * Default fetch implementation
53
+ */
54
+ private defaultFetch;
55
+ /**
56
+ * Get the URL to load translations from
57
+ */
58
+ private getLoadPath;
59
+ /**
60
+ * Check if a namespace should be tracked
61
+ */
62
+ private shouldTrackNamespace;
63
+ /**
64
+ * Track all translations in the loaded data
65
+ */
66
+ private trackTranslations;
67
+ }
68
+ /**
69
+ * Create a new instance of the ContentStorage backend
70
+ */
71
+ export declare function createContentStorageBackend(options?: ContentStoragePluginOptions): ContentStorageBackend;
72
+ export default ContentStorageBackend;
73
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,QAAQ,EACR,WAAW,EACZ,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EACV,2BAA2B,EAE5B,MAAM,SAAS,CAAC;AAUjB;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,qBAAsB,YAAW,aAAa,CAAC,2BAA2B,CAAC;IACtF,MAAM,CAAC,IAAI,EAAE,SAAS,CAAa;IACnC,IAAI,EAAE,SAAS,CAAa;IAE5B,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,UAAU,CAAkB;gBAExB,SAAS,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,2BAA2B,EAAE,eAAe,CAAC,EAAE,WAAW;IAUtG;;;OAGG;IACH,IAAI,CACF,QAAQ,EAAE,QAAQ,EAClB,cAAc,GAAE,2BAAgC,EAChD,cAAc,GAAE,WAAgB,GAC/B,IAAI;IAkCP;;;OAGG;IACH,IAAI,CACF,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,YAAY,GACrB,IAAI;IA2BP;;OAEG;YACW,gBAAgB;IA0B9B;;OAEG;YACW,YAAY;IAY1B;;OAEG;IACH,OAAO,CAAC,WAAW;IAyBnB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAW5B;;OAEG;IACH,OAAO,CAAC,iBAAiB;CA4B1B;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,CAAC,EAAE,2BAA2B,GACpC,qBAAqB,CAEvB;AAGD,eAAe,qBAAqB,CAAC"}
@@ -0,0 +1,42 @@
1
+ import type { PostProcessorModule } from 'i18next';
2
+ import type { ContentStoragePluginOptions } from './types';
3
+ /**
4
+ * ContentStorage Post-Processor
5
+ *
6
+ * This post-processor tracks translations at the point of resolution,
7
+ * capturing the actual values returned by i18next including interpolations
8
+ * and plural forms.
9
+ *
10
+ * Use this in addition to or instead of the backend plugin for more
11
+ * comprehensive tracking, especially for dynamic translations.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import i18next from 'i18next';
16
+ * import { ContentStoragePostProcessor } from '@contentstorage/i18next-plugin';
17
+ *
18
+ * i18next
19
+ * .use(new ContentStoragePostProcessor({ debug: true }))
20
+ * .init({
21
+ * // ... your config
22
+ * });
23
+ * ```
24
+ */
25
+ export declare class ContentStoragePostProcessor implements PostProcessorModule {
26
+ static type: 'postProcessor';
27
+ type: 'postProcessor';
28
+ name: string;
29
+ private options;
30
+ private isLiveMode;
31
+ constructor(options?: ContentStoragePluginOptions);
32
+ /**
33
+ * Process the translated value
34
+ * Called by i18next after translation resolution
35
+ */
36
+ process(value: string, key: string | string[], options: any, translator: any): string;
37
+ }
38
+ /**
39
+ * Create a new instance of the ContentStorage post-processor
40
+ */
41
+ export declare function createContentStoragePostProcessor(options?: ContentStoragePluginOptions): ContentStoragePostProcessor;
42
+ //# sourceMappingURL=post-processor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-processor.d.ts","sourceRoot":"","sources":["../src/post-processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,SAAS,CAAC;AAG3D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,2BAA4B,YAAW,mBAAmB;IACrE,MAAM,CAAC,IAAI,EAAE,eAAe,CAAmB;IAC/C,IAAI,EAAE,eAAe,CAAmB;IACxC,IAAI,EAAE,MAAM,CAA2B;IAEvC,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,UAAU,CAAkB;gBAExB,OAAO,GAAE,2BAAgC;IAuBrD;;;OAGG;IACH,OAAO,CACL,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,EACtB,OAAO,EAAE,GAAG,EACZ,UAAU,EAAE,GAAG,GACd,MAAM;CA+BV;AAED;;GAEG;AACH,wBAAgB,iCAAiC,CAC/C,OAAO,CAAC,EAAE,2BAA2B,GACpC,2BAA2B,CAE7B"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Entry in the memory map that tracks translation metadata
3
+ */
4
+ export interface MemoryMapEntry {
5
+ /** Set of translation keys (content IDs) that map to this value */
6
+ ids: Set<string>;
7
+ /** Type of content - always 'text' for translations */
8
+ type: 'text';
9
+ /** Optional metadata for debugging */
10
+ metadata?: {
11
+ /** Namespace where this translation was found */
12
+ namespace?: string;
13
+ /** Language code */
14
+ language?: string;
15
+ /** Timestamp when tracked */
16
+ trackedAt?: number;
17
+ };
18
+ }
19
+ /**
20
+ * Global memory map for translation tracking
21
+ * Maps translation values to their content IDs
22
+ */
23
+ export type MemoryMap = Map<string, MemoryMapEntry>;
24
+ /**
25
+ * Window interface extended with ContentStorage properties
26
+ */
27
+ export interface ContentStorageWindow extends Window {
28
+ memoryMap?: MemoryMap;
29
+ __contentStorageDebug?: boolean;
30
+ }
31
+ /**
32
+ * Plugin configuration options
33
+ */
34
+ export interface ContentStoragePluginOptions {
35
+ /**
36
+ * Your ContentStorage content key
37
+ * Used to construct CDN URLs for fetching translations
38
+ */
39
+ contentKey?: string;
40
+ /**
41
+ * Custom CDN base URL
42
+ * @default 'https://cdn.contentstorage.app'
43
+ */
44
+ cdnBaseUrl?: string;
45
+ /**
46
+ * Enable debug logging
47
+ * @default false
48
+ */
49
+ debug?: boolean;
50
+ /**
51
+ * Maximum number of entries in memoryMap
52
+ * When exceeded, oldest entries are removed
53
+ * @default 10000
54
+ */
55
+ maxMemoryMapSize?: number;
56
+ /**
57
+ * Custom function to load translations
58
+ * If provided, overrides the default CDN loading
59
+ */
60
+ loadPath?: string | ((language: string, namespace: string) => string);
61
+ /**
62
+ * Custom fetch implementation
63
+ * Useful for adding auth headers or custom logic
64
+ */
65
+ request?: (url: string, options: RequestInit) => Promise<any>;
66
+ /**
67
+ * Query parameter name for live editor detection
68
+ * @default 'contentstorage_live_editor'
69
+ */
70
+ liveEditorParam?: string;
71
+ /**
72
+ * Allow manual override of live editor mode
73
+ * Useful for testing
74
+ */
75
+ forceLiveMode?: boolean;
76
+ /**
77
+ * Namespaces to track
78
+ * If specified, only these namespaces will be tracked
79
+ * If not specified, all namespaces are tracked
80
+ */
81
+ trackNamespaces?: string[];
82
+ }
83
+ /**
84
+ * Translation data structure
85
+ * Can be a nested object with string values
86
+ */
87
+ export type TranslationData = {
88
+ [key: string]: string | TranslationData;
89
+ };
90
+ /**
91
+ * Callback for i18next backend read method
92
+ */
93
+ export type ReadCallback = (error: Error | null, data: TranslationData | boolean | null | undefined) => void;
94
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACjB,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,QAAQ,CAAC,EAAE;QACT,iDAAiD;QACjD,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,oBAAoB;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,6BAA6B;QAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAEpD;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,MAAM;IAClD,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IAEtE;;;OAGG;IACH,OAAO,CAAC,EAAE,CACR,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,WAAW,KACjB,OAAO,CAAC,GAAG,CAAC,CAAC;IAElB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,eAAe,CAAC;CACzC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,CACzB,KAAK,EAAE,KAAK,GAAG,IAAI,EACnB,IAAI,EAAE,eAAe,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,KAC/C,IAAI,CAAC"}