i18n-keyless-node 1.12.4 → 1.12.6

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.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { init, getTranslation, getAllTranslationsForAllLanguages, awaitForTranslation } from "./service";
1
+ export { init, getAllTranslationsForAllLanguages, awaitForTranslation } from "./service";
2
2
  export type { Translations, I18nKeylessNodeConfig, I18nKeylessNodeStore, TranslationOptions, I18nKeylessRequestBody, I18nKeylessAllTranslationsResponse, } from "./types";
3
3
  export { type Lang, type PrimaryLang, type I18nKeylessResponse, queue } from "i18n-keyless-core";
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { init, getTranslation, getAllTranslationsForAllLanguages, awaitForTranslation } from "./service";
1
+ export { init, getAllTranslationsForAllLanguages, awaitForTranslation } from "./service";
2
2
  export { queue } from "i18n-keyless-core";
package/dist/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "i18n-keyless-node",
3
3
  "private": false,
4
- "version": "1.12.4",
4
+ "version": "1.12.6",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
@@ -15,7 +15,7 @@
15
15
  "postpublish": "rm -rf ./dist && rm *.tgz"
16
16
  },
17
17
  "dependencies": {
18
- "i18n-keyless-core": "1.12.4"
18
+ "i18n-keyless-core": "1.12.6"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@eslint/js": "^9.9.0",
package/dist/service.d.ts CHANGED
@@ -1,18 +1,37 @@
1
1
  import { type Lang, type TranslationOptions, I18nKeylessAllTranslationsResponse } from "i18n-keyless-core";
2
- import { I18nKeylessNodeConfig, I18nKeylessNodeStore } from "types";
2
+ import { I18nKeylessNodeConfig } from "types";
3
3
  /**
4
4
  * Fetches all translations
5
5
  * @param store - The translation store
6
6
  * @returns Promise resolving to the translation response or void if failed
7
7
  */
8
- export declare function getAllTranslationsForAllLanguages(store: I18nKeylessNodeStore): Promise<I18nKeylessAllTranslationsResponse | void>;
8
+ export declare function getAllTranslationsForAllLanguages(): Promise<I18nKeylessAllTranslationsResponse | void>;
9
9
  export declare function init(newConfig: I18nKeylessNodeConfig): Promise<I18nKeylessNodeConfig>;
10
- export declare function getTranslation(key: string, currentLanguage: Lang, options?: TranslationOptions): string;
11
10
  /**
12
- * Queues a key for translation if not already translated
11
+ * Core logic for fetching/retrieving a translation asynchronously.
13
12
  * @param key - The text to translate
14
- * @param store - The translation store
13
+ * @param currentLanguage - The language to translate to
14
+ * @param options - Optional parameters for the translation process
15
+ * @returns Promise resolving to the translated string or the original key
16
+ */
17
+ declare function awaitForTranslationFn(key: string, currentLanguage: Lang, options?: TranslationOptions): Promise<string>;
18
+ /**
19
+ * **MANDATORY AWAIT / PROMISE HANDLING REQUIRED IN NODE.JS**
20
+ *
21
+ * Asynchronously retrieves a translation for a key, fetching from the backend if necessary.
22
+ * In a Node.js environment, failure to `await` this function inside a `try...catch` block
23
+ * or attach a `.catch()` handler WILL lead to an unhandled promise rejection if an error
24
+ * occurs during translation (e.g., network error, API error). This unhandled rejection
25
+ * is designed to cause a **FATAL ERROR** and **CRASH** the Node.js process to prevent
26
+ * silent failures. Ensure all calls are properly handled.
27
+ *
28
+ * **Recommendation:** Use the `@typescript-eslint/no-floating-promises` lint rule.
29
+ *
30
+ * @param key - The text to translate
31
+ * @param currentLanguage - The language to translate to
15
32
  * @param options - Optional parameters for the translation process
16
- * @throws Error if config is not initialized
33
+ * @returns A Promise resolving to the translated string or the original key if not found/on error *after handling*.
34
+ * @throws Re-throws any internal error if the promise rejection is not handled by the caller.
17
35
  */
18
- export declare const awaitForTranslation: (key: string, currentLanguage: Lang, options?: TranslationOptions) => Promise<string>;
36
+ export declare const awaitForTranslation: typeof awaitForTranslationFn;
37
+ export {};
package/dist/service.js CHANGED
@@ -1,4 +1,4 @@
1
- import { queue, getTranslationCore, api, } from "i18n-keyless-core";
1
+ import { queue, api, } from "i18n-keyless-core";
2
2
  import packageJson from "./package.json";
3
3
  const store = {
4
4
  translations: {
@@ -34,7 +34,7 @@ const store = {
34
34
  * @param store - The translation store
35
35
  * @returns Promise resolving to the translation response or void if failed
36
36
  */
37
- export async function getAllTranslationsForAllLanguages(store) {
37
+ export async function getAllTranslationsForAllLanguages() {
38
38
  const config = store.config;
39
39
  const lastRefresh = store.lastRefresh;
40
40
  const uniqueId = store.uniqueId;
@@ -73,7 +73,7 @@ export async function getAllTranslationsForAllLanguages(store) {
73
73
  }
74
74
  queue.on("empty", () => {
75
75
  // when each word is translated, fetch the translations for the current language
76
- getAllTranslationsForAllLanguages(store).then((res) => {
76
+ getAllTranslationsForAllLanguages().then((res) => {
77
77
  if (res?.ok) {
78
78
  store.translations = res.data.translations;
79
79
  }
@@ -96,110 +96,158 @@ export async function init(newConfig) {
96
96
  newConfig.addMissingTranslations = true;
97
97
  store.config = newConfig;
98
98
  store.config.onInit?.(newConfig.languages.primary);
99
- const response = await getAllTranslationsForAllLanguages(store);
99
+ const response = await getAllTranslationsForAllLanguages();
100
100
  if (response?.ok) {
101
101
  store.translations = response.data.translations;
102
102
  }
103
103
  return newConfig;
104
104
  }
105
- export function getTranslation(key, currentLanguage, options) {
106
- if (options?.debug) {
107
- console.log("getTranslation", key, currentLanguage, store.translations);
108
- }
109
- return getTranslationCore(key, {
110
- ...store,
111
- config: store.config,
112
- currentLanguage,
113
- translations: store.translations[currentLanguage],
114
- }, options);
115
- }
116
105
  /**
117
- * Queues a key for translation if not already translated
106
+ * Core logic for fetching/retrieving a translation asynchronously.
118
107
  * @param key - The text to translate
119
- * @param store - The translation store
108
+ * @param currentLanguage - The language to translate to
120
109
  * @param options - Optional parameters for the translation process
121
- * @throws Error if config is not initialized
110
+ * @returns Promise resolving to the translated string or the original key
122
111
  */
123
- export const awaitForTranslation = new Proxy(async function (key, currentLanguage, options) {
112
+ async function awaitForTranslationFn(key, currentLanguage, options) {
113
+ const config = store.config;
114
+ const translations = store.translations;
115
+ const uniqueId = store.uniqueId;
116
+ const context = options?.context;
117
+ const debug = options?.debug;
124
118
  try {
125
- const config = store.config;
126
- const translations = store.translations;
127
- const uniqueId = store.uniqueId;
128
- if (!config.API_KEY) {
129
- throw new Error("i18n-keyless: config is not initialized");
119
+ // Ensure config is initialized enough for either API call or custom handler
120
+ if (!config.API_KEY && !config.handleTranslate) {
121
+ throw new Error("i18n-keyless: config lacks API_KEY and handleTranslate. Cannot proceed.");
130
122
  }
131
- const context = options?.context;
132
- const debug = options?.debug;
133
- // if (key.length > 280) {
134
- // console.error("i18n-keyless: Key length exceeds 280 characters limit:", key);
135
- // return;
136
- // }
137
123
  if (!key) {
138
124
  return "";
139
125
  }
140
126
  if (debug) {
141
- console.log("translateKey", key, context, debug);
127
+ console.log("i18n-keyless: awaitForTranslationFn called with:", { key, currentLanguage, context, options });
142
128
  }
143
129
  const forceTemporaryLang = options?.forceTemporary?.[currentLanguage];
144
- const translation = context
145
- ? translations[currentLanguage][`${key}__${context}`]
146
- : translations[currentLanguage][key];
130
+ const translationKey = context ? `${key}__${context}` : key;
131
+ // Safe navigation for potentially undefined language store
132
+ const translation = translations[currentLanguage]?.[translationKey];
133
+ // Return existing translation if found and not forced temporary
147
134
  if (translation && !forceTemporaryLang) {
148
135
  if (debug) {
149
- console.log("translation exists", `${key}__${context}`);
136
+ console.log(`i18n-keyless: Translation found in store for key: "${translationKey}"`);
150
137
  }
151
138
  return translation;
152
139
  }
140
+ // Use custom handler if provided
153
141
  if (config.handleTranslate) {
154
- await config.handleTranslate?.(key);
155
- }
156
- else {
157
- const body = {
158
- key,
159
- context,
160
- forceTemporary: options?.forceTemporary,
161
- languages: config.languages.supported,
162
- primaryLanguage: config.languages.primary,
163
- };
164
- const apiUrl = config.API_URL || "https://api.i18n-keyless.com";
165
- const url = `${apiUrl}/translate`;
166
142
  if (debug) {
167
- console.log("fetching translation", url, body);
143
+ console.log(`i18n-keyless: Using handleTranslate for key: "${key}"`);
168
144
  }
169
- const response = await api
170
- .fetchTranslation(url, {
171
- method: "POST",
172
- headers: {
173
- "Content-Type": "application/json",
174
- Authorization: `Bearer ${config.API_KEY}`,
175
- unique_id: uniqueId || "",
176
- Version: packageJson.version,
177
- },
178
- body: JSON.stringify(body),
179
- })
180
- .then((res) => res);
181
- if (debug) {
182
- console.log("response", response);
145
+ // Expect handleTranslate to manage its own errors/state updates
146
+ await config.handleTranslate(key); // Pass only the key
147
+ // Re-check store after custom handler, maybe it populated the translation
148
+ const updatedTranslation = translations[currentLanguage]?.[translationKey];
149
+ if (updatedTranslation) {
150
+ if (debug) {
151
+ console.log(`i18n-keyless: Translation found for key "${translationKey}" after handleTranslate`);
152
+ }
153
+ return updatedTranslation;
183
154
  }
184
- if (response.message) {
185
- console.warn("i18n-keyless: ", response.message);
155
+ // If still not found after custom handler, return original key
156
+ if (debug) {
157
+ console.warn(`i18n-keyless: Translation for key "${translationKey}" still not found after handleTranslate.`);
186
158
  }
187
- return response.data.translation[currentLanguage] || key;
159
+ return key;
188
160
  }
161
+ // Proceed with API call if no custom handler
162
+ if (!config.API_KEY) {
163
+ // This should technically be caught earlier, but belt-and-suspenders
164
+ throw new Error("i18n-keyless: API_KEY is required for API translation but missing.");
165
+ }
166
+ const body = {
167
+ key,
168
+ context,
169
+ forceTemporary: options?.forceTemporary,
170
+ languages: config.languages.supported,
171
+ primaryLanguage: config.languages.primary,
172
+ };
173
+ const apiUrl = config.API_URL || "https://api.i18n-keyless.com";
174
+ const url = `${apiUrl}/translate`;
175
+ if (debug) {
176
+ console.log("i18n-keyless: Fetching translation from API:", { url, body });
177
+ }
178
+ const response = await api
179
+ .fetchTranslation(url, {
180
+ method: "POST",
181
+ headers: {
182
+ "Content-Type": "application/json",
183
+ Authorization: `Bearer ${config.API_KEY}`,
184
+ unique_id: uniqueId || "",
185
+ Version: packageJson.version,
186
+ },
187
+ body: JSON.stringify(body),
188
+ })
189
+ .then((res) => res);
190
+ if (debug) {
191
+ console.log("i18n-keyless: API response received:", response);
192
+ }
193
+ if (!response.ok) {
194
+ // Throw an error if the API response indicates failure
195
+ throw new Error(response.error || `i18n-keyless: API request failed for key "${key}"`);
196
+ }
197
+ if (response.message) {
198
+ // Log any informational messages from the API
199
+ console.warn("i18n-keyless: API message:", response.message);
200
+ }
201
+ // Return the fetched translation or the original key if not available for the current language
202
+ const fetchedTranslation = response.data?.translation?.[currentLanguage];
203
+ if (debug && !fetchedTranslation) {
204
+ console.log(`i18n-keyless: Translation for lang "${currentLanguage}" not found in API response for key "${key}". Returning original key.`);
205
+ }
206
+ return fetchedTranslation || key;
189
207
  }
190
208
  catch (error) {
191
- console.error("i18n-keyless: Error await translating key:", error);
209
+ // Log the specific error during translation attempt
210
+ console.error(`i18n-keyless: Error during awaitForTranslationFn for key "${key}":`, error);
211
+ // Re-throw the error to ensure the promise returned by this async function rejects
212
+ throw error;
192
213
  }
193
- return key;
194
- }, {
214
+ }
215
+ /**
216
+ * **MANDATORY AWAIT / PROMISE HANDLING REQUIRED IN NODE.JS**
217
+ *
218
+ * Asynchronously retrieves a translation for a key, fetching from the backend if necessary.
219
+ * In a Node.js environment, failure to `await` this function inside a `try...catch` block
220
+ * or attach a `.catch()` handler WILL lead to an unhandled promise rejection if an error
221
+ * occurs during translation (e.g., network error, API error). This unhandled rejection
222
+ * is designed to cause a **FATAL ERROR** and **CRASH** the Node.js process to prevent
223
+ * silent failures. Ensure all calls are properly handled.
224
+ *
225
+ * **Recommendation:** Use the `@typescript-eslint/no-floating-promises` lint rule.
226
+ *
227
+ * @param key - The text to translate
228
+ * @param currentLanguage - The language to translate to
229
+ * @param options - Optional parameters for the translation process
230
+ * @returns A Promise resolving to the translated string or the original key if not found/on error *after handling*.
231
+ * @throws Re-throws any internal error if the promise rejection is not handled by the caller.
232
+ */
233
+ export const awaitForTranslation = new Proxy(awaitForTranslationFn, // Target the named async function
234
+ {
195
235
  apply(target, thisArg, args) {
196
- const result = Reflect.apply(target, thisArg, args);
197
- if (result instanceof Promise) {
198
- result.catch((error) => {
199
- console.error("awaitForTranslation was not properly awaited:", error);
200
- throw error;
201
- });
202
- }
203
- return result;
236
+ // Call the actual async function
237
+ const promise = Reflect.apply(target, thisArg, args);
238
+ // Attach a catch handler. This runs ONLY if the promise REJECTS.
239
+ promise.catch((error) => {
240
+ // Log a specific error message indicating an unhandled rejection.
241
+ console.error(`i18n-keyless: FATAL: Unhandled rejection in awaitForTranslation! ` +
242
+ `The promise for key "${String(args[0])}" was rejected and not caught. ` +
243
+ `Ensure the call is wrapped in try/catch and awaited, or attach a .catch() handler. ` +
244
+ `Original error:`, error);
245
+ // Re-throw the error. In Node.js, an uncaught promise rejection
246
+ // will typically terminate the process (depending on Node version and handlers).
247
+ // This enforces handling of translation errors.
248
+ throw error;
249
+ });
250
+ // Return the original promise to the caller
251
+ return promise;
204
252
  },
205
253
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "i18n-keyless-node",
3
3
  "private": false,
4
- "version": "1.12.4",
4
+ "version": "1.12.6",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
@@ -15,7 +15,7 @@
15
15
  "postpublish": "rm -rf ./dist && rm *.tgz"
16
16
  },
17
17
  "dependencies": {
18
- "i18n-keyless-core": "1.12.4"
18
+ "i18n-keyless-core": "1.12.6"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@eslint/js": "^9.9.0",