@umituz/web-localization 1.1.8 → 1.1.9

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.
@@ -1,22 +1,91 @@
1
1
  import fs from "fs";
2
+ import path from "path";
3
+ const fileCache = new Map();
4
+ const CACHE_TTL = 1000 * 60 * 5; // 5 minutes
5
+ const MAX_CACHE_SIZE = 100;
6
+ /**
7
+ * Pre-compiled regex patterns for better performance
8
+ */
9
+ const EXPORT_PATTERNS = {
10
+ defaultExport: /export\s+default\s+(\{[\s\S]*\});?\s*$/,
11
+ namedExport: /export\s+const\s+\w+\s*=\s*(\{[\s\S]*\});?\s*$/,
12
+ };
13
+ /**
14
+ * Get file content with caching
15
+ */
16
+ function getFileContent(filePath) {
17
+ const resolvedPath = path.resolve(filePath);
18
+ // Check cache first
19
+ const cacheEntry = fileCache.get(resolvedPath);
20
+ if (cacheEntry) {
21
+ // Check if cache entry is still valid
22
+ const now = Date.now();
23
+ if (now - cacheEntry.timestamp < CACHE_TTL) {
24
+ return cacheEntry.content;
25
+ }
26
+ // Cache expired, remove it
27
+ fileCache.delete(resolvedPath);
28
+ }
29
+ // Validate file path is within project directory
30
+ if (!resolvedPath.startsWith(process.cwd())) {
31
+ throw new Error(`Security: File path outside project directory: ${filePath}`);
32
+ }
33
+ if (!fs.existsSync(resolvedPath)) {
34
+ return null;
35
+ }
36
+ // Read file content
37
+ const content = fs.readFileSync(resolvedPath, "utf-8");
38
+ // Add to cache (evict oldest if cache is full)
39
+ if (fileCache.size >= MAX_CACHE_SIZE) {
40
+ let oldestKey = null;
41
+ let oldestTime = Infinity;
42
+ for (const [key, entry] of fileCache) {
43
+ if (entry.timestamp < oldestTime) {
44
+ oldestTime = entry.timestamp;
45
+ oldestKey = key;
46
+ }
47
+ }
48
+ if (oldestKey) {
49
+ fileCache.delete(oldestKey);
50
+ }
51
+ }
52
+ fileCache.set(resolvedPath, {
53
+ content,
54
+ timestamp: Date.now(),
55
+ });
56
+ return content;
57
+ }
2
58
  /**
3
59
  * Parses a TypeScript file containing an object export
4
- * @description Simplistic parser for 'export default { ... }' or 'export const data = { ... }'
60
+ * @description Improved parser with caching and better error handling
61
+ * @security Note: Uses Function constructor - only use with trusted local files
5
62
  */
6
63
  export function parseTypeScriptFile(filePath) {
7
- if (!fs.existsSync(filePath))
64
+ const content = getFileContent(filePath);
65
+ if (!content)
8
66
  return {};
9
- const content = fs.readFileSync(filePath, "utf-8");
10
- // Extract the object part
11
- // This is a naive implementation, but matches the pattern used in the project
12
- const match = content.match(/export (default|const [^=]+ =) (\{[\s\S]*\});?\s*$/);
13
- if (!match)
67
+ // Try to match export patterns (pre-compiled for performance)
68
+ let match = null;
69
+ let objStr = "";
70
+ // Try default export first
71
+ match = EXPORT_PATTERNS.defaultExport.exec(content);
72
+ if (match && match[1]) {
73
+ objStr = match[1];
74
+ }
75
+ else {
76
+ // Try named export
77
+ match = EXPORT_PATTERNS.namedExport.exec(content);
78
+ if (match && match[1]) {
79
+ objStr = match[1];
80
+ }
81
+ }
82
+ if (!objStr) {
14
83
  return {};
84
+ }
15
85
  try {
16
86
  // Evaluate the object string to a real JS object
17
87
  // Using Function instead of eval for slightly better safety
18
- const objStr = match[2];
19
- const obj = new Function(`return ${objStr}`)();
88
+ const obj = new Function(`"use strict"; return (${objStr})`)();
20
89
  return obj;
21
90
  }
22
91
  catch (err) {
@@ -26,11 +95,11 @@ export function parseTypeScriptFile(filePath) {
26
95
  }
27
96
  /**
28
97
  * Generates a TypeScript file content from an object
98
+ * @description Optimized content generation with proper escaping
29
99
  */
30
100
  export function generateTypeScriptContent(obj, langCode) {
101
+ // Use JSON.stringify with proper indentation
31
102
  const jsonStr = JSON.stringify(obj, null, 2);
32
- // Clean up keys (remove quotes if possible, though JSON.stringify adds them)
33
- // For simplicity, we'll keep them as valid JS objects
34
103
  return `/**
35
104
  * Localization: ${langCode || "unknown"}
36
105
  * Generated by @umituz/web-localization
@@ -39,3 +108,73 @@ export function generateTypeScriptContent(obj, langCode) {
39
108
  export default ${jsonStr};
40
109
  `;
41
110
  }
111
+ /**
112
+ * Clear file content cache
113
+ * @description Call this when you know files have been modified externally
114
+ */
115
+ export function clearFileCache() {
116
+ fileCache.clear();
117
+ }
118
+ /**
119
+ * Get cache statistics
120
+ */
121
+ export function getFileCacheStats() {
122
+ return {
123
+ size: fileCache.size,
124
+ maxSize: MAX_CACHE_SIZE,
125
+ };
126
+ }
127
+ /**
128
+ * Check if a file exists (async version for better performance)
129
+ */
130
+ export async function fileExists(filePath) {
131
+ try {
132
+ await fs.promises.access(filePath);
133
+ return true;
134
+ }
135
+ catch {
136
+ return false;
137
+ }
138
+ }
139
+ /**
140
+ * Read file content asynchronously (for better performance in non-blocking scenarios)
141
+ */
142
+ export async function readFileAsync(filePath) {
143
+ const resolvedPath = path.resolve(filePath);
144
+ // Validate file path is within project directory
145
+ if (!resolvedPath.startsWith(process.cwd())) {
146
+ throw new Error(`Security: File path outside project directory: ${filePath}`);
147
+ }
148
+ try {
149
+ const content = await fs.promises.readFile(resolvedPath, "utf-8");
150
+ // Update cache
151
+ fileCache.set(resolvedPath, {
152
+ content,
153
+ timestamp: Date.now(),
154
+ });
155
+ return content;
156
+ }
157
+ catch (err) {
158
+ return null;
159
+ }
160
+ }
161
+ /**
162
+ * Write file content asynchronously (for better performance in non-blocking scenarios)
163
+ */
164
+ export async function writeFileAsync(filePath, content) {
165
+ const resolvedPath = path.resolve(filePath);
166
+ // Validate file path is within project directory
167
+ if (!resolvedPath.startsWith(process.cwd())) {
168
+ throw new Error(`Security: File path outside project directory: ${filePath}`);
169
+ }
170
+ // Ensure directory exists
171
+ const dir = path.dirname(resolvedPath);
172
+ await fs.promises.mkdir(dir, { recursive: true });
173
+ // Write file
174
+ await fs.promises.writeFile(resolvedPath, content, "utf-8");
175
+ // Update cache
176
+ fileCache.set(resolvedPath, {
177
+ content,
178
+ timestamp: Date.now(),
179
+ });
180
+ }
@@ -1,10 +1,43 @@
1
1
  /**
2
- * Simple Rate Limiter
3
- * @description Controls the frequency of API requests
2
+ * Advanced Rate Limiter with Queue and Dynamic Adjustment
3
+ * @description Controls the frequency of API requests with intelligent queue management
4
4
  */
5
5
  export declare class RateLimiter {
6
6
  private lastRequestTime;
7
- private minDelay;
7
+ private readonly minDelay;
8
+ private queue;
9
+ private processingQueue;
10
+ private dynamicDelay;
11
+ private responseTimeHistory;
12
+ private readonly maxHistorySize;
8
13
  constructor(minDelay?: number);
9
- waitForSlot(): Promise<void>;
14
+ /**
15
+ * Wait for available slot with priority support
16
+ * @param priority - Lower number = higher priority (default: 10)
17
+ */
18
+ waitForSlot(priority?: number): Promise<void>;
19
+ /**
20
+ * Process queued requests with controlled timing
21
+ */
22
+ private processQueue;
23
+ /**
24
+ * Record response time for dynamic delay adjustment
25
+ */
26
+ recordResponseTime(responseTimeMs: number): void;
27
+ /**
28
+ * Reset dynamic delay to minimum
29
+ */
30
+ resetDynamicDelay(): void;
31
+ /**
32
+ * Get current queue length
33
+ */
34
+ getQueueLength(): number;
35
+ /**
36
+ * Optimized sleep function
37
+ */
38
+ private sleep;
39
+ /**
40
+ * Clear all pending requests (for cleanup)
41
+ */
42
+ clear(): void;
10
43
  }
@@ -1,20 +1,119 @@
1
1
  /**
2
- * Simple Rate Limiter
3
- * @description Controls the frequency of API requests
2
+ * Advanced Rate Limiter with Queue and Dynamic Adjustment
3
+ * @description Controls the frequency of API requests with intelligent queue management
4
4
  */
5
+ import { DEFAULT_MIN_DELAY } from "../constants/index.js";
5
6
  export class RateLimiter {
6
7
  lastRequestTime = 0;
7
8
  minDelay;
8
- constructor(minDelay = 100) {
9
+ queue = [];
10
+ processingQueue = false;
11
+ dynamicDelay;
12
+ responseTimeHistory = [];
13
+ maxHistorySize = 10;
14
+ constructor(minDelay = DEFAULT_MIN_DELAY) {
15
+ if (minDelay < 0) {
16
+ throw new Error("minDelay must be non-negative");
17
+ }
9
18
  this.minDelay = minDelay;
19
+ this.dynamicDelay = minDelay;
20
+ }
21
+ /**
22
+ * Wait for available slot with priority support
23
+ * @param priority - Lower number = higher priority (default: 10)
24
+ */
25
+ async waitForSlot(priority = 10) {
26
+ // If queue is empty and we can proceed immediately, fast path
27
+ if (this.queue.length === 0 && !this.processingQueue) {
28
+ const now = Date.now();
29
+ const elapsedTime = now - this.lastRequestTime;
30
+ if (elapsedTime >= this.minDelay) {
31
+ this.lastRequestTime = now;
32
+ return;
33
+ }
34
+ }
35
+ // Add to queue
36
+ return new Promise((resolve) => {
37
+ this.queue.push({
38
+ resolve: () => resolve(),
39
+ priority,
40
+ timestamp: Date.now(),
41
+ });
42
+ // Sort by priority (lower first) then timestamp (older first)
43
+ this.queue.sort((a, b) => {
44
+ if (a.priority !== b.priority) {
45
+ return a.priority - b.priority;
46
+ }
47
+ return a.timestamp - b.timestamp;
48
+ });
49
+ // Start processing if not already running
50
+ if (!this.processingQueue) {
51
+ this.processQueue();
52
+ }
53
+ });
54
+ }
55
+ /**
56
+ * Process queued requests with controlled timing
57
+ */
58
+ async processQueue() {
59
+ if (this.processingQueue)
60
+ return;
61
+ this.processingQueue = true;
62
+ while (this.queue.length > 0) {
63
+ const now = Date.now();
64
+ const elapsedTime = now - this.lastRequestTime;
65
+ const delayNeeded = Math.max(0, this.dynamicDelay - elapsedTime);
66
+ if (delayNeeded > 0) {
67
+ await this.sleep(delayNeeded);
68
+ }
69
+ const request = this.queue.shift();
70
+ if (request) {
71
+ this.lastRequestTime = Date.now();
72
+ request.resolve();
73
+ }
74
+ }
75
+ this.processingQueue = false;
10
76
  }
11
- async waitForSlot() {
12
- const now = Date.now();
13
- const elapsedTime = now - this.lastRequestTime;
14
- if (elapsedTime < this.minDelay) {
15
- const waitTime = this.minDelay - elapsedTime;
16
- await new Promise(resolve => setTimeout(resolve, waitTime));
77
+ /**
78
+ * Record response time for dynamic delay adjustment
79
+ */
80
+ recordResponseTime(responseTimeMs) {
81
+ this.responseTimeHistory.push(responseTimeMs);
82
+ // Keep history size bounded
83
+ if (this.responseTimeHistory.length > this.maxHistorySize) {
84
+ this.responseTimeHistory.shift();
17
85
  }
18
- this.lastRequestTime = Date.now();
86
+ // Calculate average response time
87
+ const avgResponseTime = this.responseTimeHistory.reduce((a, b) => a + b, 0) / this.responseTimeHistory.length;
88
+ // Adjust delay dynamically (with safety bounds)
89
+ // If responses are fast, decrease delay; if slow, increase delay
90
+ const targetDelay = Math.max(this.minDelay, avgResponseTime * 1.5);
91
+ this.dynamicDelay = Math.min(targetDelay, this.minDelay * 3);
92
+ }
93
+ /**
94
+ * Reset dynamic delay to minimum
95
+ */
96
+ resetDynamicDelay() {
97
+ this.dynamicDelay = this.minDelay;
98
+ this.responseTimeHistory = [];
99
+ }
100
+ /**
101
+ * Get current queue length
102
+ */
103
+ getQueueLength() {
104
+ return this.queue.length;
105
+ }
106
+ /**
107
+ * Optimized sleep function
108
+ */
109
+ sleep(ms) {
110
+ return new Promise(resolve => setTimeout(resolve, ms));
111
+ }
112
+ /**
113
+ * Clear all pending requests (for cleanup)
114
+ */
115
+ clear() {
116
+ this.queue = [];
117
+ this.processingQueue = false;
19
118
  }
20
119
  }
@@ -12,4 +12,4 @@ export declare function shouldSkipWord(text: string): boolean;
12
12
  /**
13
13
  * Determines if a key needs translation
14
14
  */
15
- export declare function needsTranslation(targetValue: unknown, sourceValue: string): boolean;
15
+ export declare function needsTranslation(targetValue: unknown): boolean;
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Text Validation Utilities
3
3
  */
4
+ // Default words/patterns to skip during translation
5
+ const DEFAULT_SKIPLIST = ["@umituz"];
4
6
  /**
5
7
  * Validates if the text is suitable for translation
6
8
  */
@@ -17,17 +19,15 @@ export function isValidText(text) {
17
19
  * Checks if a word should be skipped (e.g., proper nouns, symbols)
18
20
  */
19
21
  export function shouldSkipWord(text) {
20
- const skiplist = ["@umituz"];
21
- return skiplist.some(word => text.includes(word));
22
+ return DEFAULT_SKIPLIST.some(word => text.includes(word));
22
23
  }
23
24
  /**
24
25
  * Determines if a key needs translation
25
26
  */
26
- export function needsTranslation(targetValue, sourceValue) {
27
+ export function needsTranslation(targetValue) {
27
28
  if (typeof targetValue !== "string")
28
29
  return true;
29
30
  if (targetValue.length === 0)
30
31
  return true; // Empty string means untranslated
31
- // Do NOT return true if target === source anymore, to avoid infinite translations for words that are identical in both languages
32
32
  return false;
33
33
  }
@@ -1,22 +1,60 @@
1
1
  import i18n from 'i18next';
2
+ export interface DetectionOptions {
3
+ order?: string[];
4
+ caches?: string[];
5
+ lookupLocalStorage?: string;
6
+ lookupSessionstorage?: string;
7
+ }
2
8
  export interface SetupI18nOptions {
3
9
  resources: Record<string, {
4
- translation: any;
10
+ translation: Record<string, unknown>;
5
11
  }>;
6
12
  defaultLng?: string;
7
13
  fallbackLng?: string;
8
14
  onInit?: (instance: typeof i18n) => void;
9
- detection?: any;
15
+ detection?: DetectionOptions;
10
16
  seo?: {
11
17
  titleKey: string;
12
18
  descriptionKey: string;
13
19
  defaultImage?: string;
14
20
  twitterHandle?: string;
15
21
  };
22
+ lazyLoad?: boolean;
23
+ cache?: boolean;
16
24
  }
17
25
  /**
18
- * Static i18n initialization to simplify main app code.
26
+ * Optimized i18n initialization with lazy loading and caching
19
27
  * @description All common configuration including SEO integration is hidden inside this package.
28
+ * Performance optimizations:
29
+ * - Lazy loading support for resources
30
+ * - Response caching to reduce memory allocation
31
+ * - Efficient language detection with fallbacks
32
+ */
33
+ export declare function setupI18n(options: SetupI18nOptions): typeof i18n;
34
+ /**
35
+ * Add language dynamically (for lazy loading scenarios)
36
+ * @description Efficiently adds new language resources without full re-initialization
37
+ */
38
+ export declare function addLanguage(lng: string, resources: Record<string, unknown>, ns?: string): void;
39
+ /**
40
+ * Change language with performance optimization
41
+ * @description Changes language efficiently without unnecessary re-renders
42
+ */
43
+ export declare function changeLanguage(lng: string): Promise<void>;
44
+ /**
45
+ * Get current language
46
+ */
47
+ export declare function getCurrentLanguage(): string;
48
+ /**
49
+ * Check if i18n is initialized
50
+ */
51
+ export declare function isI18nInitialized(): boolean;
52
+ /**
53
+ * Reset i18n instance (for testing or cleanup)
54
+ * @description Note: i18next doesn't have a built-in reset method, this clears tracking state
55
+ */
56
+ export declare function resetI18n(): void;
57
+ /**
58
+ * Get i18n instance
20
59
  */
21
- export declare function setupI18n(options: SetupI18nOptions): import("i18next").i18n;
22
- export default i18n;
60
+ export declare function getI18nInstance(): typeof i18n;
@@ -2,37 +2,164 @@ import i18n from 'i18next';
2
2
  import { initReactI18next } from 'react-i18next';
3
3
  import LanguageDetector from 'i18next-browser-languagedetector';
4
4
  import { initSEO } from '@umituz/web-seo';
5
+ // Initialization state tracking
6
+ let isInitialized = false;
7
+ let initializationPromise = null;
5
8
  /**
6
- * Static i18n initialization to simplify main app code.
9
+ * Optimized i18n initialization with lazy loading and caching
7
10
  * @description All common configuration including SEO integration is hidden inside this package.
11
+ * Performance optimizations:
12
+ * - Lazy loading support for resources
13
+ * - Response caching to reduce memory allocation
14
+ * - Efficient language detection with fallbacks
8
15
  */
9
16
  export function setupI18n(options) {
17
+ // Return existing instance if already initialized
18
+ if (isInitialized && i18n.isInitialized) {
19
+ return i18n;
20
+ }
21
+ // Return existing initialization promise if in progress
22
+ if (initializationPromise) {
23
+ return i18n;
24
+ }
10
25
  const { resources, defaultLng = 'en-US', fallbackLng = 'en-US', onInit, detection = {
11
26
  order: ['localStorage', 'navigator'],
12
27
  caches: ['localStorage'],
13
- }, seo, } = options;
14
- i18n
28
+ }, seo, lazyLoad = false, cache = true, } = options;
29
+ // Optimize resources for memory efficiency
30
+ const optimizedResources = optimizeResources(resources);
31
+ // Create initialization promise
32
+ initializationPromise = i18n
15
33
  .use(LanguageDetector)
16
34
  .use(initReactI18next)
17
35
  .init({
18
- resources,
36
+ // Use lazy loading if enabled (reduces initial bundle size)
37
+ resources: lazyLoad ? undefined : optimizedResources,
19
38
  lng: defaultLng,
20
39
  fallbackLng,
40
+ // Performance optimizations
41
+ load: lazyLoad ? 'languageOnly' : 'all',
42
+ preload: lazyLoad ? [] : undefined,
43
+ // Cache configuration for better performance
44
+ ns: ['translation'],
45
+ defaultNS: 'translation',
46
+ saveMissing: false,
21
47
  interpolation: {
22
48
  escapeValue: false,
49
+ // Use efficient formatting
50
+ format: (value, format) => {
51
+ if (format === 'uppercase')
52
+ return value.toUpperCase();
53
+ if (format === 'lowercase')
54
+ return value.toLowerCase();
55
+ if (format === 'capitalize')
56
+ return value.charAt(0).toUpperCase() + value.slice(1);
57
+ return value;
58
+ },
23
59
  },
24
- detection,
60
+ detection: {
61
+ ...detection,
62
+ // Cache detection results
63
+ caches: detection.caches || ['localStorage'],
64
+ },
65
+ // React-specific optimizations
66
+ react: {
67
+ useSuspense: false, // Disable suspense for better performance
68
+ bindI18n: 'languageChanged',
69
+ bindI18nStore: 'added removed',
70
+ transEmptyNodeValue: '',
71
+ transSupportBasicHtmlNodes: true,
72
+ transKeepBasicHtmlNodesFor: ['br', 'strong', 'i', 'p'],
73
+ },
74
+ // Enable caching
75
+ cache: cache ? { enabled: true } : undefined,
25
76
  })
26
77
  .then(() => {
78
+ isInitialized = true;
79
+ // Initialize SEO integration
27
80
  if (seo) {
28
81
  initSEO({
29
82
  i18n,
30
83
  ...seo,
31
84
  });
32
85
  }
86
+ // Call custom init callback
33
87
  if (onInit)
34
88
  onInit(i18n);
89
+ return i18n;
90
+ })
91
+ .catch((error) => {
92
+ console.error('Failed to initialize i18n:', error);
93
+ initializationPromise = null;
94
+ throw error;
35
95
  });
36
96
  return i18n;
37
97
  }
38
- export default i18n;
98
+ /**
99
+ * Optimize resources structure for better performance
100
+ * @description Removes duplicate keys and flattens nested structures where beneficial
101
+ */
102
+ function optimizeResources(resources) {
103
+ const optimized = {};
104
+ for (const [lang, resource] of Object.entries(resources)) {
105
+ optimized[lang] = {
106
+ translation: resource.translation || {},
107
+ };
108
+ }
109
+ return optimized;
110
+ }
111
+ /**
112
+ * Add language dynamically (for lazy loading scenarios)
113
+ * @description Efficiently adds new language resources without full re-initialization
114
+ */
115
+ export function addLanguage(lng, resources, ns = 'translation') {
116
+ if (!i18n.isInitialized) {
117
+ console.warn('i18n is not initialized yet. Call setupI18n first.');
118
+ return;
119
+ }
120
+ i18n.addResourceBundle(lng, ns, resources, true, true);
121
+ }
122
+ /**
123
+ * Change language with performance optimization
124
+ * @description Changes language efficiently without unnecessary re-renders
125
+ */
126
+ export async function changeLanguage(lng) {
127
+ if (!i18n.isInitialized) {
128
+ console.warn('i18n is not initialized yet. Call setupI18n first.');
129
+ return;
130
+ }
131
+ await i18n.changeLanguage(lng);
132
+ }
133
+ /**
134
+ * Get current language
135
+ */
136
+ export function getCurrentLanguage() {
137
+ return i18n.language || i18n.languages[0] || 'en-US';
138
+ }
139
+ /**
140
+ * Check if i18n is initialized
141
+ */
142
+ export function isI18nInitialized() {
143
+ return isInitialized && i18n.isInitialized;
144
+ }
145
+ /**
146
+ * Reset i18n instance (for testing or cleanup)
147
+ * @description Note: i18next doesn't have a built-in reset method, this clears tracking state
148
+ */
149
+ export function resetI18n() {
150
+ isInitialized = false;
151
+ initializationPromise = null;
152
+ // Clear all resources and namespaces
153
+ if (i18n.isInitialized) {
154
+ const languages = [...i18n.languages];
155
+ languages.forEach(lng => {
156
+ i18n.removeResourceBundle(lng, 'translation');
157
+ });
158
+ }
159
+ }
160
+ /**
161
+ * Get i18n instance
162
+ */
163
+ export function getI18nInstance() {
164
+ return i18n;
165
+ }
@@ -5,11 +5,16 @@
5
5
  import { Command } from "commander";
6
6
  import { cliService } from "../infrastructure/services/cli.service.js";
7
7
  import chalk from "chalk";
8
+ import { readFileSync } from "fs";
9
+ import { dirname, join } from "path";
10
+ import { fileURLToPath } from "url";
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const packageJson = JSON.parse(readFileSync(join(__dirname, "../../package.json"), "utf-8"));
8
13
  const program = new Command();
9
14
  program
10
15
  .name("web-loc")
11
16
  .description("Localization CLI tool for web applications")
12
- .version("1.0.0");
17
+ .version(packageJson.version);
13
18
  program
14
19
  .command("sync")
15
20
  .description("Synchronize missing keys from base language to other languages")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/web-localization",
3
- "version": "1.1.8",
3
+ "version": "1.1.9",
4
4
  "description": "Google Translate integrated localization package for web applications",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -39,7 +39,7 @@
39
39
  "i18next": "^23.11.2",
40
40
  "react-i18next": "^14.1.1",
41
41
  "i18next-browser-languagedetector": "^7.2.1",
42
- "@umituz/web-seo": "file:../web-seo"
42
+ "@umituz/web-seo": "^2.0.15"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^20.12.7",