i18n-keyless-node 1.10.4 → 1.10.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.
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "i18n-keyless",
3
+ "private": false,
4
+ "type": "module",
5
+ "version": "1.10.6",
6
+ "workspaces": [
7
+ "packages/*"
8
+ ],
9
+ "scripts": {
10
+ "build": "npm run build:core && npm run build:react",
11
+ "build:core": "npm run build:lib --workspace=i18n-keyless-core",
12
+ "build:react": "npm run build:lib --workspace=i18n-keyless-react",
13
+ "clean": "npm run clean:core && npm run clean:react",
14
+ "clean:core": "rm -rf packages/core/dist",
15
+ "clean:react": "rm -rf packages/react/dist",
16
+ "test": "npm run test --workspace=i18n-keyless-react",
17
+ "test:watch": "npm run test:watch --workspace=i18n-keyless-react",
18
+ "test:coverage": "npm run test:coverage --workspace=i18n-keyless-react"
19
+ },
20
+ "devDependencies": {
21
+ "typescript": "^5.5.3",
22
+ "typescript-eslint": "^8.0.1"
23
+ }
24
+ }
@@ -0,0 +1,5 @@
1
+ export declare const api: {
2
+ fetchTranslation: (url: string, options: RequestInit) => Promise<any>;
3
+ fetchTranslationsForOneLanguage: (url: string, options: RequestInit) => Promise<any>;
4
+ fetchAllTranslationsForAllLanguages: (url: string, options: RequestInit) => Promise<any>;
5
+ };
@@ -0,0 +1,23 @@
1
+ export const api = {
2
+ fetchTranslation: async (url, options) => {
3
+ return fetch(url, options)
4
+ .then((res) => res.json())
5
+ .catch((err) => {
6
+ return { ok: false, error: err.message };
7
+ });
8
+ },
9
+ fetchTranslationsForOneLanguage: async (url, options) => {
10
+ return fetch(url, options)
11
+ .then((res) => res.json())
12
+ .catch((err) => {
13
+ return { ok: false, error: err.message };
14
+ });
15
+ },
16
+ fetchAllTranslationsForAllLanguages: async (url, options) => {
17
+ return fetch(url, options)
18
+ .then((res) => res.json())
19
+ .catch((err) => {
20
+ return { ok: false, error: err.message };
21
+ });
22
+ },
23
+ };
@@ -0,0 +1,2 @@
1
+ export type { Lang, PrimaryLang, Translations, HandleTranslateFunction, GetAllTranslationsFunction, GetAllTranslationsForAllLanguagesFunction, LanguagesConfig, LastRefresh, UniqueId, I18nKeylessRequestBody, I18nKeylessResponse, I18nKeylessAllTranslationsResponse, FetchTranslationParams, TranslationOptions, } from "./types";
2
+ export { getTranslationCore, getAllTranslationsFromLanguage, queue } from "./service";
@@ -0,0 +1 @@
1
+ export { getTranslationCore, getAllTranslationsFromLanguage, queue } from "./service";
@@ -0,0 +1,24 @@
1
+ type Task<T> = () => Promise<T>;
2
+ declare class EventEmitter {
3
+ private events;
4
+ on(event: string, callback: () => void): void;
5
+ off(event: string, callback: () => void): void;
6
+ emit(event: string): void;
7
+ }
8
+ export default class MyPQueue extends EventEmitter {
9
+ private queue;
10
+ private pending;
11
+ private readonly concurrency;
12
+ private processing;
13
+ constructor(options?: {
14
+ concurrency?: number;
15
+ });
16
+ add<T>(task: Task<T>, options?: {
17
+ priority?: number;
18
+ id?: string;
19
+ }): Promise<T>;
20
+ private processNext;
21
+ get size(): number;
22
+ get isPaused(): boolean;
23
+ }
24
+ export {};
@@ -0,0 +1,108 @@
1
+ // https://github.com/sindresorhus/p-queue/issues/145#issuecomment-882068004
2
+ // p-queu import is broken, so here is the smalle implementation of it
3
+ class EventEmitter {
4
+ constructor() {
5
+ Object.defineProperty(this, "events", {
6
+ enumerable: true,
7
+ configurable: true,
8
+ writable: true,
9
+ value: {}
10
+ });
11
+ }
12
+ on(event, callback) {
13
+ if (!this.events[event]) {
14
+ this.events[event] = [];
15
+ }
16
+ this.events[event].push(callback);
17
+ }
18
+ off(event, callback) {
19
+ if (!this.events[event])
20
+ return;
21
+ this.events[event] = this.events[event].filter((cb) => cb !== callback);
22
+ }
23
+ emit(event) {
24
+ if (!this.events[event])
25
+ return;
26
+ this.events[event].forEach((callback) => callback());
27
+ }
28
+ }
29
+ export default class MyPQueue extends EventEmitter {
30
+ constructor(options = {}) {
31
+ super();
32
+ Object.defineProperty(this, "queue", {
33
+ enumerable: true,
34
+ configurable: true,
35
+ writable: true,
36
+ value: []
37
+ });
38
+ Object.defineProperty(this, "pending", {
39
+ enumerable: true,
40
+ configurable: true,
41
+ writable: true,
42
+ value: 0
43
+ });
44
+ Object.defineProperty(this, "concurrency", {
45
+ enumerable: true,
46
+ configurable: true,
47
+ writable: true,
48
+ value: void 0
49
+ });
50
+ Object.defineProperty(this, "processing", {
51
+ enumerable: true,
52
+ configurable: true,
53
+ writable: true,
54
+ value: false
55
+ });
56
+ this.concurrency = options.concurrency ?? Infinity;
57
+ }
58
+ add(task, options = {}) {
59
+ const { priority = 0, id = String(Date.now()) } = options;
60
+ // If task with same ID exists, return its promise
61
+ const existingTask = this.queue.find((item) => item.id === id);
62
+ if (existingTask) {
63
+ return existingTask.task();
64
+ }
65
+ return new Promise((resolve, reject) => {
66
+ const wrappedTask = async () => {
67
+ try {
68
+ const result = await task();
69
+ resolve(result);
70
+ return result;
71
+ }
72
+ catch (error) {
73
+ reject(error);
74
+ throw error;
75
+ }
76
+ finally {
77
+ this.pending--;
78
+ this.processNext();
79
+ }
80
+ };
81
+ this.queue.push({ task: wrappedTask, priority, id });
82
+ this.queue.sort((a, b) => b.priority - a.priority);
83
+ this.processNext();
84
+ });
85
+ }
86
+ async processNext() {
87
+ if (this.processing)
88
+ return;
89
+ this.processing = true;
90
+ while (this.queue.length > 0 && this.pending < this.concurrency) {
91
+ const task = this.queue.shift();
92
+ if (task) {
93
+ this.pending++;
94
+ task.task().catch(() => { });
95
+ }
96
+ }
97
+ this.processing = false;
98
+ if (this.queue.length === 0 && this.pending === 0) {
99
+ this.emit("empty");
100
+ }
101
+ }
102
+ get size() {
103
+ return this.queue.length;
104
+ }
105
+ get isPaused() {
106
+ return false;
107
+ }
108
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "i18n-keyless-core",
3
+ "private": false,
4
+ "version": "1.10.6",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "prepublishOnly": "rm -rf ./dist && tsc --project tsconfig.json && npm pack",
14
+ "test": "echo 'no test for core'",
15
+ "postpublish": "rm -rf ./dist && rm *.tgz"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^22.5.5",
19
+ "@types/react": "^19.0.8",
20
+ "@types/react-dom": "^19.0.3"
21
+ }
22
+ }
@@ -0,0 +1,27 @@
1
+ import type { Lang, TranslationOptions, I18nKeylessResponse, FetchTranslationParams } from "./types";
2
+ import MyPQueue from "./my-pqueue";
3
+ export declare const queue: MyPQueue;
4
+ /**
5
+ * Gets a translation for the specified key from the store
6
+ * @param key - The translation key (text in primary language)
7
+ * @param store - The translation store containing translations and config
8
+ * @param options - Optional parameters for translation retrieval
9
+ * @returns The translated text or the original key if not found
10
+ * @throws Error if config is not initialized
11
+ */
12
+ export declare function getTranslationCore(key: string, store: FetchTranslationParams, options?: TranslationOptions): string;
13
+ /**
14
+ * Queues a key for translation if not already translated
15
+ * @param key - The text to translate
16
+ * @param store - The translation store
17
+ * @param options - Optional parameters for the translation process
18
+ * @throws Error if config is not initialized
19
+ */
20
+ export declare function translateKey(key: string, store: FetchTranslationParams, options?: TranslationOptions): void;
21
+ /**
22
+ * Fetches all translations for a target language
23
+ * @param targetLanguage - The language code to fetch translations for
24
+ * @param store - The translation store
25
+ * @returns Promise resolving to the translation response or void if failed
26
+ */
27
+ export declare function getAllTranslationsFromLanguage(targetLanguage: Lang, store: FetchTranslationParams): Promise<I18nKeylessResponse | void>;
@@ -0,0 +1,163 @@
1
+ import MyPQueue from "./my-pqueue";
2
+ import packageJson from "./package.json";
3
+ import { api } from "./api";
4
+ export const queue = new MyPQueue({ concurrency: 5 });
5
+ /**
6
+ * Gets a translation for the specified key from the store
7
+ * @param key - The translation key (text in primary language)
8
+ * @param store - The translation store containing translations and config
9
+ * @param options - Optional parameters for translation retrieval
10
+ * @returns The translated text or the original key if not found
11
+ * @throws Error if config is not initialized
12
+ */
13
+ export function getTranslationCore(key, store, options) {
14
+ const currentLanguage = store.currentLanguage;
15
+ const config = store.config;
16
+ const translations = store.translations;
17
+ if (!config.API_KEY) {
18
+ throw new Error("i18n-keyless: config is not initialized");
19
+ }
20
+ if (currentLanguage === config.languages.primary) {
21
+ return key;
22
+ }
23
+ if (options?.forceTemporary?.[currentLanguage]) {
24
+ translateKey(key, store, options);
25
+ }
26
+ const context = options?.context;
27
+ const translation = context ? translations[`${key}__${context}`] : translations[key];
28
+ if (!translation) {
29
+ translateKey(key, store, options);
30
+ }
31
+ return translation || key;
32
+ }
33
+ const translating = {};
34
+ /**
35
+ * Queues a key for translation if not already translated
36
+ * @param key - The text to translate
37
+ * @param store - The translation store
38
+ * @param options - Optional parameters for the translation process
39
+ * @throws Error if config is not initialized
40
+ */
41
+ export function translateKey(key, store, options) {
42
+ const currentLanguage = store.currentLanguage;
43
+ const config = store.config;
44
+ const translations = store.translations;
45
+ const uniqueId = store.uniqueId;
46
+ if (!config.API_KEY) {
47
+ throw new Error("i18n-keyless: config is not initialized");
48
+ }
49
+ const context = options?.context;
50
+ const debug = options?.debug;
51
+ // if (key.length > 280) {
52
+ // console.error("i18n-keyless: Key length exceeds 280 characters limit:", key);
53
+ // return;
54
+ // }
55
+ if (!key) {
56
+ return;
57
+ }
58
+ if (debug) {
59
+ console.log("translateKey", key, context, debug);
60
+ }
61
+ const forceTemporaryLang = options?.forceTemporary?.[currentLanguage];
62
+ const translation = context ? translations[`${key}__${context}`] : translations[key];
63
+ if (translation && !forceTemporaryLang) {
64
+ if (debug) {
65
+ console.log("translation exists", `${key}__${context}`);
66
+ }
67
+ return;
68
+ }
69
+ queue.add(async () => {
70
+ try {
71
+ if (translating[key]) {
72
+ return;
73
+ }
74
+ else {
75
+ translating[key] = true;
76
+ }
77
+ if (config.handleTranslate) {
78
+ await config.handleTranslate?.(key);
79
+ }
80
+ else {
81
+ const body = {
82
+ key,
83
+ context,
84
+ forceTemporary: options?.forceTemporary,
85
+ languages: config.languages.supported,
86
+ primaryLanguage: config.languages.primary,
87
+ };
88
+ const apiUrl = config.API_URL || "https://api.i18n-keyless.com";
89
+ const url = `${apiUrl}/translate`;
90
+ if (debug) {
91
+ console.log("fetching translation", url, body);
92
+ }
93
+ const response = await api
94
+ .fetchTranslation(url, {
95
+ method: "POST",
96
+ headers: {
97
+ "Content-Type": "application/json",
98
+ Authorization: `Bearer ${config.API_KEY}`,
99
+ unique_id: uniqueId || "",
100
+ Version: packageJson.version,
101
+ },
102
+ body: JSON.stringify(body),
103
+ })
104
+ .then((res) => res);
105
+ if (debug) {
106
+ console.log("response", response);
107
+ }
108
+ if (response.message) {
109
+ console.warn("i18n-keyless: ", response.message);
110
+ }
111
+ }
112
+ translating[key] = false;
113
+ return;
114
+ }
115
+ catch (error) {
116
+ console.error("i18n-keyless: Error translating key:", error);
117
+ translating[key] = false;
118
+ }
119
+ }, { priority: 1, id: key });
120
+ }
121
+ /**
122
+ * Fetches all translations for a target language
123
+ * @param targetLanguage - The language code to fetch translations for
124
+ * @param store - The translation store
125
+ * @returns Promise resolving to the translation response or void if failed
126
+ */
127
+ export async function getAllTranslationsFromLanguage(targetLanguage, store) {
128
+ const config = store.config;
129
+ const lastRefresh = store.lastRefresh;
130
+ const uniqueId = store.uniqueId;
131
+ if (!config.API_KEY) {
132
+ console.error("i18n-keyless: No config found");
133
+ return;
134
+ }
135
+ // if (config.languages.primary === targetLanguage) {
136
+ // return;
137
+ // }
138
+ try {
139
+ const response = config.getAllTranslations
140
+ ? await config.getAllTranslations()
141
+ : await api
142
+ .fetchTranslationsForOneLanguage(`${config.API_URL || "https://api.i18n-keyless.com"}/translate/${targetLanguage}?last_refresh=${lastRefresh}`, {
143
+ method: "GET",
144
+ headers: {
145
+ "Content-Type": "application/json",
146
+ Authorization: `Bearer ${config.API_KEY}`,
147
+ Version: packageJson.version,
148
+ unique_id: uniqueId || "",
149
+ },
150
+ })
151
+ .then((res) => res);
152
+ if (!response.ok) {
153
+ throw new Error(response.error);
154
+ }
155
+ if (response.message) {
156
+ console.warn("i18n-keyless: ", response.message);
157
+ }
158
+ return response;
159
+ }
160
+ catch (error) {
161
+ console.error("i18n-keyless: fetch all translations error:", error);
162
+ }
163
+ }
@@ -0,0 +1,94 @@
1
+ export type PrimaryLang = "fr" | "en";
2
+ export type Lang = "fr" | "en" | "nl" | "it" | "de" | "es" | "pl" | "pt" | "ro" | "sv" | "tr" | "ja" | "cn" | "ru" | "ko" | "ar";
3
+ export type Translations = Record<string, string>;
4
+ export type HandleTranslateFunction = (key: string) => Promise<{
5
+ ok: boolean;
6
+ message: string;
7
+ }>;
8
+ export type GetAllTranslationsFunction = () => Promise<I18nKeylessResponse>;
9
+ export type GetAllTranslationsForAllLanguagesFunction = () => Promise<I18nKeylessAllTranslationsResponse>;
10
+ export type LastRefresh = string | null;
11
+ export type UniqueId = string | null;
12
+ export type LanguagesConfig = {
13
+ /**
14
+ * the language used by the developer
15
+ */
16
+ primary: PrimaryLang;
17
+ /**
18
+ * the languages supported for the user.
19
+ * For now we support:
20
+ * fr, nl, it, de, es, pl, pt, ro, sv, tr, ja, cn, ru, ko, ar
21
+ *
22
+ * If you need more, please reach out to @ambroselli_io on X/Twitter or by mail at arnaud.ambroselli.io@gmail.com
23
+ */
24
+ supported: Lang[];
25
+ /**
26
+ * if the user's langauge is not supported, the fallback language will be used
27
+ */
28
+ fallback?: Lang;
29
+ /**
30
+ * the language to use when the app is initialized
31
+ */
32
+ initWithDefault?: Lang;
33
+ };
34
+ export type TranslationOptions = {
35
+ /**
36
+ * The context of the translation.
37
+ * Useful for ambiguous translations, like "8 heures" in French could be "8 AM" or "8 hours".
38
+ * You'll find it useful when it occurs to you, don't worry :)
39
+ */
40
+ context?: string;
41
+ /**
42
+ * Could be helpful if something weird happens with this particular key.
43
+ */
44
+ debug?: boolean;
45
+ /**
46
+ * If the proposed translation from AI is not satisfactory,
47
+ * you can use this field to setup your own translation.
48
+ * You can leave it there forever, or remove it once your translation is saved.
49
+ */
50
+ forceTemporary?: Partial<Record<Lang, string>>;
51
+ };
52
+ export interface I18nKeylessRequestBody {
53
+ key: string;
54
+ context?: string;
55
+ forceTemporary?: TranslationOptions["forceTemporary"];
56
+ languages: LanguagesConfig["supported"];
57
+ primaryLanguage: LanguagesConfig["primary"];
58
+ }
59
+ export interface I18nKeylessResponse {
60
+ ok: boolean;
61
+ data: {
62
+ translations: Translations;
63
+ uniqueId: UniqueId;
64
+ lastRefresh: LastRefresh;
65
+ };
66
+ error: string;
67
+ message: string;
68
+ }
69
+ export interface I18nKeylessAllTranslationsResponse {
70
+ ok: boolean;
71
+ data: {
72
+ translations: Record<Lang, Translations>;
73
+ uniqueId: UniqueId;
74
+ lastRefresh: LastRefresh;
75
+ };
76
+ error: string;
77
+ message: string;
78
+ }
79
+ export type FetchTranslationParams = {
80
+ uniqueId: UniqueId;
81
+ lastRefresh: LastRefresh;
82
+ currentLanguage: Lang;
83
+ config: {
84
+ API_KEY: string;
85
+ API_URL?: string;
86
+ languages: LanguagesConfig;
87
+ addMissingTranslations?: boolean;
88
+ debug?: boolean;
89
+ handleTranslate?: HandleTranslateFunction;
90
+ getAllTranslations?: GetAllTranslationsFunction;
91
+ getAllTranslationsForAllLanguages?: GetAllTranslationsForAllLanguagesFunction;
92
+ };
93
+ translations: Translations;
94
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export { init, getTranslation, getAllTranslationsForAllLanguages } from "./service";
2
+ export type { Translations, I18nKeylessNodeConfig, I18nKeylessNodeStore, TranslationOptions, I18nKeylessRequestBody, I18nKeylessAllTranslationsResponse, } from "./types";
3
+ export { type Lang, type PrimaryLang, type I18nKeylessResponse, queue } from "i18n-keyless-core";
@@ -0,0 +1,2 @@
1
+ export { init, getTranslation, getAllTranslationsForAllLanguages } from "./service";
2
+ export { queue } from "i18n-keyless-core";
@@ -0,0 +1,10 @@
1
+ import { type Lang, type TranslationOptions, I18nKeylessAllTranslationsResponse } from "i18n-keyless-core";
2
+ import { I18nKeylessNodeConfig, I18nKeylessNodeStore } from "types";
3
+ /**
4
+ * Fetches all translations
5
+ * @param store - The translation store
6
+ * @returns Promise resolving to the translation response or void if failed
7
+ */
8
+ export declare function getAllTranslationsForAllLanguages(store: I18nKeylessNodeStore): Promise<I18nKeylessAllTranslationsResponse | void>;
9
+ export declare function init(newConfig: I18nKeylessNodeConfig): Promise<I18nKeylessNodeConfig>;
10
+ export declare function getTranslation(key: string, currentLanguage: Lang, options?: TranslationOptions): string;
@@ -0,0 +1,116 @@
1
+ import { queue, getTranslationCore, } from "i18n-keyless-core";
2
+ import { api } from "i18n-keyless-core/api";
3
+ import packageJson from "../../package.json";
4
+ const store = {
5
+ translations: {
6
+ fr: {},
7
+ en: {},
8
+ es: {},
9
+ nl: {},
10
+ it: {},
11
+ de: {},
12
+ pl: {},
13
+ pt: {},
14
+ ro: {},
15
+ sv: {},
16
+ tr: {},
17
+ ja: {},
18
+ cn: {},
19
+ ru: {},
20
+ ko: {},
21
+ ar: {},
22
+ },
23
+ uniqueId: "",
24
+ lastRefresh: "",
25
+ config: {
26
+ API_KEY: "",
27
+ languages: {
28
+ primary: "fr",
29
+ supported: ["fr"],
30
+ },
31
+ },
32
+ };
33
+ /**
34
+ * Fetches all translations
35
+ * @param store - The translation store
36
+ * @returns Promise resolving to the translation response or void if failed
37
+ */
38
+ export async function getAllTranslationsForAllLanguages(store) {
39
+ const config = store.config;
40
+ const lastRefresh = store.lastRefresh;
41
+ const uniqueId = store.uniqueId;
42
+ if (!config.API_KEY) {
43
+ console.error("i18n-keyless: No config found");
44
+ return;
45
+ }
46
+ // if (config.languages.primary === targetLanguage) {
47
+ // return;
48
+ // }
49
+ try {
50
+ const response = config.getAllTranslationsForAllLanguages
51
+ ? await config.getAllTranslationsForAllLanguages()
52
+ : await api
53
+ .fetchAllTranslationsForAllLanguages(`${config.API_URL || "https://api.i18n-keyless.com"}/translate/?last_refresh=${lastRefresh}`, {
54
+ method: "GET",
55
+ headers: {
56
+ "Content-Type": "application/json",
57
+ Authorization: `Bearer ${config.API_KEY}`,
58
+ Version: packageJson.version,
59
+ unique_id: uniqueId || "",
60
+ },
61
+ })
62
+ .then((res) => res);
63
+ if (!response.ok) {
64
+ throw new Error(response.error);
65
+ }
66
+ if (response.message) {
67
+ console.warn("i18n-keyless: ", response.message);
68
+ }
69
+ return response;
70
+ }
71
+ catch (error) {
72
+ console.error("i18n-keyless: fetch all translations error:", error);
73
+ }
74
+ }
75
+ queue.on("empty", () => {
76
+ // when each word is translated, fetch the translations for the current language
77
+ getAllTranslationsForAllLanguages(store).then((res) => {
78
+ if (res?.ok) {
79
+ store.translations = res.data.translations;
80
+ }
81
+ });
82
+ });
83
+ export async function init(newConfig) {
84
+ if (!newConfig.languages) {
85
+ throw new Error("i18n-keyless: languages is required");
86
+ }
87
+ if (!newConfig.languages.primary) {
88
+ throw new Error("i18n-keyless: primary is required");
89
+ }
90
+ if (!newConfig.getAllTranslationsForAllLanguages || !newConfig.handleTranslate) {
91
+ if (!newConfig.API_KEY) {
92
+ if (!newConfig.API_URL) {
93
+ throw new Error("i18n-keyless: you didn't provide an API_KEY nor an API_URL nor a handleTranslate + getAllTranslationsForAllLanguages function. You need to provide one of them to make i18n-keyless work");
94
+ }
95
+ }
96
+ }
97
+ newConfig.addMissingTranslations = true;
98
+ store.config = newConfig;
99
+ store.config.onInit?.(newConfig.languages.primary);
100
+ const response = await getAllTranslationsForAllLanguages(store);
101
+ if (response?.ok) {
102
+ store.translations = response.data.translations;
103
+ }
104
+ return newConfig;
105
+ }
106
+ export function getTranslation(key, currentLanguage, options) {
107
+ if (options?.debug) {
108
+ console.log("getTranslation", key, currentLanguage, store.translations);
109
+ }
110
+ return getTranslationCore(key, {
111
+ ...store,
112
+ config: store.config,
113
+ currentLanguage,
114
+ translations: store.translations[currentLanguage],
115
+ }, options);
116
+ }
@@ -0,0 +1,123 @@
1
+ import { Lang, PrimaryLang } from "i18n-keyless-core";
2
+ export type Translations = Record<string, string>;
3
+ export interface I18nKeylessNodeConfig {
4
+ /**
5
+ * The API key for the i18n-keyless API
6
+ *
7
+ * contact @ambroselli_io on X/Twitter or by mail at arnaud.ambroselli.io@gmail.com for getting one
8
+ */
9
+ API_KEY: string;
10
+ /**
11
+ * Your own API URL for the i18n-keyless API
12
+ *
13
+ * You'll need to implement two routes on your server
14
+ * - GET /translate/:lang
15
+ * - POST /translate -- with a body of { key: string }
16
+ */
17
+ API_URL?: string;
18
+ /**
19
+ * The languages config
20
+ *
21
+ * primary: the language used by the developer
22
+ * supported: the languages supported for the user
23
+ * fallback: if the user's langauge is not supported, the fallback language will be used
24
+ * initWithDefault: the language to use when the app is initialized for the first time
25
+ */
26
+ languages: {
27
+ /**
28
+ * the language used by the developer
29
+ */
30
+ primary: PrimaryLang;
31
+ /**
32
+ * the languages supported for the user.
33
+ * For now we support:
34
+ * fr, nl, it, de, es, pl, pt, ro, sv, tr, ja, cn, ru, ko, ar
35
+ *
36
+ * If you need more, please reach out to @ambroselli_io on X/Twitter or by mail at arnaud.ambroselli.io@gmail.com
37
+ */
38
+ supported: Lang[];
39
+ };
40
+ addMissingTranslations?: true;
41
+ /**
42
+ * called right after the store is initialized, maybe to hide screensplash. or init specific default langauge for dayjs, or whatever
43
+ */
44
+ onInit?: (lang: Lang) => void;
45
+ /**
46
+ * if true, all the logs will be displayed in the console
47
+ */
48
+ debug?: boolean;
49
+ /**
50
+ * if this function exists, it will be called instead of the API call
51
+ * if this function doesn't exist, the default behavior is to call the API
52
+ * therefore you would need either to
53
+ * - use this `handleTranslate` function to handle the translation with your own API
54
+ * - not use this `handleTranslate` function, and use the built in API call with API_KEY filled
55
+ * - not use this `handleTranslate` function nor API_KEY key, and provide your own API_URL
56
+ */
57
+ handleTranslate?: (key: string) => Promise<{
58
+ ok: boolean;
59
+ message: string;
60
+ }>;
61
+ /**
62
+ * if this function exists, it will be called instead of the API call
63
+ * if this function doesn't exist, the default behavior is to call the API, with the API_KEY
64
+ * therefore you need either to
65
+ * - use this `getAllTranslationsForAllLanguages` function to handle the translation with your own API
66
+ * - not use this `getAllTranslationsForAllLanguages` function, and use the built in API call with the API_KEY filled
67
+ * - not use this `getAllTranslationsForAllLanguages` function nor API_KEY key, and provide your own API_URL
68
+ */
69
+ getAllTranslationsForAllLanguages?: () => Promise<I18nKeylessAllTranslationsResponse>;
70
+ }
71
+ export interface I18nKeylessNodeStore {
72
+ /**
73
+ * the unique id of the consumer of i18n-keyless API, to help identify the usage API side
74
+ */
75
+ uniqueId: string | null;
76
+ /**
77
+ * the last refresh of the translations, to only fetch the new ones if any
78
+ */
79
+ lastRefresh: string | null;
80
+ /**
81
+ * the translations fetched from i18n-keyless' API
82
+ */
83
+ translations: Record<Lang, Translations>;
84
+ /**
85
+ * i18n-keyless' config
86
+ */
87
+ config: I18nKeylessNodeConfig;
88
+ }
89
+ export type TranslationOptions = {
90
+ /**
91
+ * The context of the translation.
92
+ * Useful for ambiguous translations, like "8 heures" in French could be "8 AM" or "8 hours".
93
+ * You'll find it useful when it occurs to you, don't worry :)
94
+ */
95
+ context?: string;
96
+ /**
97
+ * Could be helpful if something weird happens with this particular key.
98
+ */
99
+ debug?: boolean;
100
+ /**
101
+ * If the proposed translation from AI is not satisfactory,
102
+ * you can use this field to setup your own translation.
103
+ * You can leave it there forever, or remove it once your translation is saved.
104
+ */
105
+ forceTemporary?: Partial<Record<Lang, string>>;
106
+ };
107
+ export interface I18nKeylessRequestBody {
108
+ key: string;
109
+ context?: string;
110
+ forceTemporary?: TranslationOptions["forceTemporary"];
111
+ languages: I18nKeylessNodeConfig["languages"]["supported"];
112
+ primaryLanguage: I18nKeylessNodeConfig["languages"]["primary"];
113
+ }
114
+ export interface I18nKeylessAllTranslationsResponse {
115
+ ok: boolean;
116
+ data: {
117
+ translations: Record<Lang, Translations>;
118
+ uniqueId: string | null;
119
+ lastRefresh: string | null;
120
+ };
121
+ error: string;
122
+ message: string;
123
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "i18n-keyless-node",
3
3
  "private": false,
4
- "version": "1.10.4",
4
+ "version": "1.10.6",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
@@ -11,7 +11,8 @@
11
11
  ],
12
12
  "scripts": {
13
13
  "prepublishOnly": "rm -rf ./dist && tsc --project tsconfig.json && npm pack",
14
- "test": "echo 'no test for node'"
14
+ "test": "echo 'no test for node'",
15
+ "postpublish": "rm -rf ./dist && rm *.tgz"
15
16
  },
16
17
  "dependencies": {
17
18
  "i18n-keyless-core": "latest"
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export { init, getTranslation } from "./service";
2
- export { type I18nConfig, type Lang, type PrimaryLang, type Translations, type TranslationStore, type TranslationStoreState, type I18nKeylessRequestBody, type I18nKeylessResponse, type TranslationOptions, getAllTranslationsForAllLanguages, validateLanguage, queue, } from "i18n-keyless-core";
package/dist/index.js DELETED
@@ -1,2 +0,0 @@
1
- export { init, getTranslation } from "./service";
2
- export { getAllTranslationsForAllLanguages, validateLanguage, queue, } from "i18n-keyless-core";
package/dist/service.d.ts DELETED
@@ -1,3 +0,0 @@
1
- import { type Lang, type NodeConfig, type TranslationOptions } from "i18n-keyless-core";
2
- export declare function init(newConfig: NodeConfig): Promise<NodeConfig>;
3
- export declare function getTranslation(key: string, currentLanguage: Lang, options?: TranslationOptions): string;
package/dist/service.js DELETED
@@ -1,41 +0,0 @@
1
- import { queue, getTranslationCore, getAllTranslationsForAllLanguages, } from "i18n-keyless-core";
2
- const store = {
3
- translations: {},
4
- uniqueId: "",
5
- lastRefresh: "",
6
- config: null,
7
- setTranslations: () => { },
8
- };
9
- queue.on("empty", () => {
10
- // when each word is translated, fetch the translations for the current language
11
- getAllTranslationsForAllLanguages(store).then(store.setTranslations);
12
- });
13
- export async function init(newConfig) {
14
- if (!newConfig.languages) {
15
- throw new Error("i18n-keyless: languages is required");
16
- }
17
- if (!newConfig.languages.primary) {
18
- throw new Error("i18n-keyless: primary is required");
19
- }
20
- if (!newConfig.languages.fallback) {
21
- newConfig.languages.fallback = newConfig.languages.primary;
22
- }
23
- if (!newConfig.getAllTranslationsForAllLanguages || !newConfig.handleTranslate) {
24
- if (!newConfig.API_KEY) {
25
- if (!newConfig.API_URL) {
26
- throw new Error("i18n-keyless: you didn't provide an API_KEY nor an API_URL nor a handleTranslate + getAllTranslationsForAllLanguages function. You need to provide one of them to make i18n-keyless work");
27
- }
28
- }
29
- }
30
- if (newConfig.addMissingTranslations !== false) {
31
- // default to true
32
- newConfig.addMissingTranslations = true;
33
- }
34
- store.config = newConfig;
35
- store.config.onInit?.(newConfig.languages.primary);
36
- getAllTranslationsForAllLanguages(store).then(store.setTranslations);
37
- return newConfig;
38
- }
39
- export function getTranslation(key, currentLanguage, options) {
40
- return getTranslationCore(key, { ...store, currentLanguage }, options);
41
- }