@kopynator/core 1.0.0 → 1.0.2

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.mts CHANGED
@@ -3,7 +3,8 @@ interface KopyConfig {
3
3
  projectId?: string;
4
4
  baseUrl?: string;
5
5
  defaultLocale?: string;
6
- mode?: 'local' | 'live';
6
+ mode?: 'local' | 'live' | 'hybrid';
7
+ languages?: string[];
7
8
  }
8
9
  interface TranslationResponse {
9
10
  [key: string]: string;
@@ -14,6 +15,7 @@ declare class KopyFetcher {
14
15
  constructor(config: KopyConfig);
15
16
  fetchTranslations(locale: string): Promise<TranslationResponse>;
16
17
  fetchAvailableLanguages(): Promise<string[]>;
18
+ fetchLocal(locale: string): Promise<TranslationResponse>;
17
19
  }
18
20
 
19
21
  declare class KopyCompiler {
@@ -28,17 +30,20 @@ declare class KopyCompiler {
28
30
  interface KopyStorage {
29
31
  get(key: string): string | null;
30
32
  set(key: string, value: string): void;
33
+ clear(): void;
31
34
  }
32
35
  declare class MemoryStorage implements KopyStorage {
33
36
  private cache;
34
37
  get(key: string): string | null;
35
38
  set(key: string, value: string): void;
39
+ clear(): void;
36
40
  }
37
41
  declare class LocalStorageWrapper implements KopyStorage {
38
42
  get(key: string): string | null;
39
43
  set(key: string, value: string): void;
44
+ clear(): void;
40
45
  }
41
- declare const getBestStorage: () => KopyStorage;
46
+ declare const getBestStorage: (usePersistence?: boolean) => KopyStorage;
42
47
 
43
48
  declare class Kopynator {
44
49
  private config;
@@ -46,14 +51,21 @@ declare class Kopynator {
46
51
  private storage;
47
52
  private translations;
48
53
  private currentLocale;
54
+ private availableLanguages;
49
55
  constructor(config: KopyConfig);
50
56
  /**
51
57
  * Initialize the SDK and load initial translations.
52
58
  */
53
59
  init(): Promise<void>;
60
+ /**
61
+ * Fetch and update the list of available languages from the API.
62
+ */
63
+ refreshAvailableLanguages(): Promise<string[]>;
64
+ getLanguages(): string[];
65
+ getCurrentLocale(): string;
54
66
  /**
55
67
  * Load translations for a specific locale.
56
- * Priority: Memory > Storage (Cache) > API
68
+ * Priority: Local Assets (Base) -> API (Override)
57
69
  */
58
70
  loadLocale(locale: string): Promise<void>;
59
71
  /**
package/dist/index.d.ts CHANGED
@@ -3,7 +3,8 @@ interface KopyConfig {
3
3
  projectId?: string;
4
4
  baseUrl?: string;
5
5
  defaultLocale?: string;
6
- mode?: 'local' | 'live';
6
+ mode?: 'local' | 'live' | 'hybrid';
7
+ languages?: string[];
7
8
  }
8
9
  interface TranslationResponse {
9
10
  [key: string]: string;
@@ -14,6 +15,7 @@ declare class KopyFetcher {
14
15
  constructor(config: KopyConfig);
15
16
  fetchTranslations(locale: string): Promise<TranslationResponse>;
16
17
  fetchAvailableLanguages(): Promise<string[]>;
18
+ fetchLocal(locale: string): Promise<TranslationResponse>;
17
19
  }
18
20
 
19
21
  declare class KopyCompiler {
@@ -28,17 +30,20 @@ declare class KopyCompiler {
28
30
  interface KopyStorage {
29
31
  get(key: string): string | null;
30
32
  set(key: string, value: string): void;
33
+ clear(): void;
31
34
  }
32
35
  declare class MemoryStorage implements KopyStorage {
33
36
  private cache;
34
37
  get(key: string): string | null;
35
38
  set(key: string, value: string): void;
39
+ clear(): void;
36
40
  }
37
41
  declare class LocalStorageWrapper implements KopyStorage {
38
42
  get(key: string): string | null;
39
43
  set(key: string, value: string): void;
44
+ clear(): void;
40
45
  }
41
- declare const getBestStorage: () => KopyStorage;
46
+ declare const getBestStorage: (usePersistence?: boolean) => KopyStorage;
42
47
 
43
48
  declare class Kopynator {
44
49
  private config;
@@ -46,14 +51,21 @@ declare class Kopynator {
46
51
  private storage;
47
52
  private translations;
48
53
  private currentLocale;
54
+ private availableLanguages;
49
55
  constructor(config: KopyConfig);
50
56
  /**
51
57
  * Initialize the SDK and load initial translations.
52
58
  */
53
59
  init(): Promise<void>;
60
+ /**
61
+ * Fetch and update the list of available languages from the API.
62
+ */
63
+ refreshAvailableLanguages(): Promise<string[]>;
64
+ getLanguages(): string[];
65
+ getCurrentLocale(): string;
54
66
  /**
55
67
  * Load translations for a specific locale.
56
- * Priority: Memory > Storage (Cache) > API
68
+ * Priority: Local Assets (Base) -> API (Override)
57
69
  */
58
70
  loadLocale(locale: string): Promise<void>;
59
71
  /**
package/dist/index.js CHANGED
@@ -29,21 +29,36 @@ __export(index_exports, {
29
29
  });
30
30
  module.exports = __toCommonJS(index_exports);
31
31
 
32
+ // src/utils/flatten.ts
33
+ function flatten(obj, prefix = "") {
34
+ return Object.keys(obj).reduce((acc, k) => {
35
+ const pre = prefix.length ? prefix + "." : "";
36
+ if (typeof obj[k] === "object" && obj[k] !== null && !Array.isArray(obj[k])) {
37
+ Object.assign(acc, flatten(obj[k], pre + k));
38
+ } else {
39
+ acc[pre + k] = String(obj[k]);
40
+ }
41
+ return acc;
42
+ }, {});
43
+ }
44
+
32
45
  // src/services/fetcher.ts
33
46
  var KopyFetcher = class {
34
47
  constructor(config) {
35
48
  this.config = config;
36
- this.baseUrl = config.baseUrl || "http://localhost:7300/api";
49
+ this.baseUrl = config.baseUrl || "https://api.kopynator.com";
37
50
  }
38
51
  baseUrl;
39
52
  async fetchTranslations(locale) {
40
53
  try {
41
- const url = `${this.baseUrl}/tokens/fetch?token=${this.config.apiKey}&lang=${locale}&nested=false&includeLangKey=false`;
54
+ const url = `${this.baseUrl}/tokens/fetch?token=${this.config.apiKey}&langs=${locale}&nested=false&includeLangKey=false`;
42
55
  const response = await fetch(url);
43
56
  if (!response.ok) {
44
57
  throw new Error(`Failed to fetch translations: ${response.statusText}`);
45
58
  }
46
- return await response.json();
59
+ const data = await response.json();
60
+ const flattened = flatten(data);
61
+ return flattened;
47
62
  } catch (error) {
48
63
  console.error("[Kopynator] Error fetching translations:", error);
49
64
  return {};
@@ -62,6 +77,20 @@ var KopyFetcher = class {
62
77
  return [];
63
78
  }
64
79
  }
80
+ async fetchLocal(locale) {
81
+ try {
82
+ const url = `/assets/i18n/${locale}.json`;
83
+ const response = await fetch(url);
84
+ if (!response.ok) {
85
+ throw new Error(`Local translations not found: ${response.statusText}`);
86
+ }
87
+ const data = await response.json();
88
+ const flattened = flatten(data);
89
+ return flattened;
90
+ } catch (error) {
91
+ return {};
92
+ }
93
+ }
65
94
  };
66
95
 
67
96
  // src/utils/compiler.ts
@@ -102,6 +131,9 @@ var MemoryStorage = class {
102
131
  set(key, value) {
103
132
  this.cache.set(key, value);
104
133
  }
134
+ clear() {
135
+ this.cache.clear();
136
+ }
105
137
  };
106
138
  var LocalStorageWrapper = class {
107
139
  get(key) {
@@ -110,10 +142,13 @@ var LocalStorageWrapper = class {
110
142
  set(key, value) {
111
143
  if (typeof localStorage !== "undefined") localStorage.setItem(key, value);
112
144
  }
145
+ clear() {
146
+ if (typeof localStorage !== "undefined") localStorage.clear();
147
+ }
113
148
  };
114
149
  var memoryStorage = new MemoryStorage();
115
- var getBestStorage = () => {
116
- if (typeof localStorage !== "undefined") {
150
+ var getBestStorage = (usePersistence = false) => {
151
+ if (usePersistence && typeof localStorage !== "undefined") {
117
152
  return new LocalStorageWrapper();
118
153
  }
119
154
  return memoryStorage;
@@ -124,39 +159,75 @@ var Kopynator = class {
124
159
  constructor(config) {
125
160
  this.config = config;
126
161
  this.fetcher = new KopyFetcher(config);
127
- this.storage = getBestStorage();
162
+ this.storage = getBestStorage(false);
128
163
  this.currentLocale = config.defaultLocale || "en";
164
+ this.availableLanguages = config.languages || [];
165
+ if (!this.config.mode) {
166
+ this.config.mode = "hybrid";
167
+ }
129
168
  }
130
169
  fetcher;
131
170
  storage;
132
171
  translations = {};
133
172
  currentLocale;
173
+ availableLanguages = [];
134
174
  /**
135
175
  * Initialize the SDK and load initial translations.
136
176
  */
137
177
  async init() {
138
- await this.loadLocale(this.currentLocale);
178
+ const tasks = [this.loadLocale(this.currentLocale)];
179
+ if (this.config.mode !== "local") {
180
+ tasks.push(this.refreshAvailableLanguages());
181
+ }
182
+ await Promise.all(tasks);
183
+ }
184
+ /**
185
+ * Fetch and update the list of available languages from the API.
186
+ */
187
+ async refreshAvailableLanguages() {
188
+ if (!this.config.apiKey || this.config.mode === "local") {
189
+ return this.availableLanguages;
190
+ }
191
+ const langs = await this.fetcher.fetchAvailableLanguages();
192
+ if (langs && langs.length > 0) {
193
+ this.availableLanguages = langs;
194
+ }
195
+ return this.availableLanguages;
196
+ }
197
+ getLanguages() {
198
+ return this.availableLanguages;
199
+ }
200
+ getCurrentLocale() {
201
+ return this.currentLocale;
139
202
  }
140
203
  /**
141
204
  * Load translations for a specific locale.
142
- * Priority: Memory > Storage (Cache) > API
205
+ * Priority: Local Assets (Base) -> API (Override)
143
206
  */
144
207
  async loadLocale(locale) {
145
- const identifier = this.config.projectId || this.config.apiKey;
146
- if (this.translations[locale]) return;
147
- const cached = this.storage.get(`kopy_${identifier}_${locale}`);
148
- if (cached) {
208
+ if (this.translations[locale] && Object.keys(this.translations[locale]).length > 0) {
209
+ return;
210
+ }
211
+ this.translations[locale] = {};
212
+ try {
213
+ const localData = await this.fetcher.fetchLocal(locale);
214
+ if (localData && Object.keys(localData).length > 0) {
215
+ this.translations[locale] = { ...localData };
216
+ }
217
+ } catch (e) {
218
+ }
219
+ if (this.config.mode !== "local") {
149
220
  try {
150
- this.translations[locale] = JSON.parse(cached);
151
- return;
221
+ const remoteData = await this.fetcher.fetchTranslations(locale);
222
+ if (remoteData && Object.keys(remoteData).length > 0) {
223
+ this.translations[locale] = {
224
+ ...this.translations[locale],
225
+ ...remoteData
226
+ };
227
+ }
152
228
  } catch (e) {
153
- console.warn("[Kopynator] Failed to parse cached translations");
154
229
  }
155
230
  }
156
- const data = await this.fetcher.fetchTranslations(locale);
157
- this.translations[locale] = data;
158
- this.storage.set(`kopy_${identifier}_${locale}`, JSON.stringify(data));
159
- this.currentLocale = locale;
160
231
  }
161
232
  /**
162
233
  * Change current locale and load it if necessary.
@@ -170,7 +241,10 @@ var Kopynator = class {
170
241
  */
171
242
  translate(key, params = {}) {
172
243
  const localeTranslations = this.translations[this.currentLocale] || {};
173
- const template = localeTranslations[key] || key;
244
+ const template = localeTranslations[key];
245
+ if (!template) {
246
+ return key;
247
+ }
174
248
  return KopyCompiler.compile(template, params);
175
249
  }
176
250
  /**
package/dist/index.mjs CHANGED
@@ -1,18 +1,33 @@
1
+ // src/utils/flatten.ts
2
+ function flatten(obj, prefix = "") {
3
+ return Object.keys(obj).reduce((acc, k) => {
4
+ const pre = prefix.length ? prefix + "." : "";
5
+ if (typeof obj[k] === "object" && obj[k] !== null && !Array.isArray(obj[k])) {
6
+ Object.assign(acc, flatten(obj[k], pre + k));
7
+ } else {
8
+ acc[pre + k] = String(obj[k]);
9
+ }
10
+ return acc;
11
+ }, {});
12
+ }
13
+
1
14
  // src/services/fetcher.ts
2
15
  var KopyFetcher = class {
3
16
  constructor(config) {
4
17
  this.config = config;
5
- this.baseUrl = config.baseUrl || "http://localhost:7300/api";
18
+ this.baseUrl = config.baseUrl || "https://api.kopynator.com";
6
19
  }
7
20
  baseUrl;
8
21
  async fetchTranslations(locale) {
9
22
  try {
10
- const url = `${this.baseUrl}/tokens/fetch?token=${this.config.apiKey}&lang=${locale}&nested=false&includeLangKey=false`;
23
+ const url = `${this.baseUrl}/tokens/fetch?token=${this.config.apiKey}&langs=${locale}&nested=false&includeLangKey=false`;
11
24
  const response = await fetch(url);
12
25
  if (!response.ok) {
13
26
  throw new Error(`Failed to fetch translations: ${response.statusText}`);
14
27
  }
15
- return await response.json();
28
+ const data = await response.json();
29
+ const flattened = flatten(data);
30
+ return flattened;
16
31
  } catch (error) {
17
32
  console.error("[Kopynator] Error fetching translations:", error);
18
33
  return {};
@@ -31,6 +46,20 @@ var KopyFetcher = class {
31
46
  return [];
32
47
  }
33
48
  }
49
+ async fetchLocal(locale) {
50
+ try {
51
+ const url = `/assets/i18n/${locale}.json`;
52
+ const response = await fetch(url);
53
+ if (!response.ok) {
54
+ throw new Error(`Local translations not found: ${response.statusText}`);
55
+ }
56
+ const data = await response.json();
57
+ const flattened = flatten(data);
58
+ return flattened;
59
+ } catch (error) {
60
+ return {};
61
+ }
62
+ }
34
63
  };
35
64
 
36
65
  // src/utils/compiler.ts
@@ -71,6 +100,9 @@ var MemoryStorage = class {
71
100
  set(key, value) {
72
101
  this.cache.set(key, value);
73
102
  }
103
+ clear() {
104
+ this.cache.clear();
105
+ }
74
106
  };
75
107
  var LocalStorageWrapper = class {
76
108
  get(key) {
@@ -79,10 +111,13 @@ var LocalStorageWrapper = class {
79
111
  set(key, value) {
80
112
  if (typeof localStorage !== "undefined") localStorage.setItem(key, value);
81
113
  }
114
+ clear() {
115
+ if (typeof localStorage !== "undefined") localStorage.clear();
116
+ }
82
117
  };
83
118
  var memoryStorage = new MemoryStorage();
84
- var getBestStorage = () => {
85
- if (typeof localStorage !== "undefined") {
119
+ var getBestStorage = (usePersistence = false) => {
120
+ if (usePersistence && typeof localStorage !== "undefined") {
86
121
  return new LocalStorageWrapper();
87
122
  }
88
123
  return memoryStorage;
@@ -93,39 +128,75 @@ var Kopynator = class {
93
128
  constructor(config) {
94
129
  this.config = config;
95
130
  this.fetcher = new KopyFetcher(config);
96
- this.storage = getBestStorage();
131
+ this.storage = getBestStorage(false);
97
132
  this.currentLocale = config.defaultLocale || "en";
133
+ this.availableLanguages = config.languages || [];
134
+ if (!this.config.mode) {
135
+ this.config.mode = "hybrid";
136
+ }
98
137
  }
99
138
  fetcher;
100
139
  storage;
101
140
  translations = {};
102
141
  currentLocale;
142
+ availableLanguages = [];
103
143
  /**
104
144
  * Initialize the SDK and load initial translations.
105
145
  */
106
146
  async init() {
107
- await this.loadLocale(this.currentLocale);
147
+ const tasks = [this.loadLocale(this.currentLocale)];
148
+ if (this.config.mode !== "local") {
149
+ tasks.push(this.refreshAvailableLanguages());
150
+ }
151
+ await Promise.all(tasks);
152
+ }
153
+ /**
154
+ * Fetch and update the list of available languages from the API.
155
+ */
156
+ async refreshAvailableLanguages() {
157
+ if (!this.config.apiKey || this.config.mode === "local") {
158
+ return this.availableLanguages;
159
+ }
160
+ const langs = await this.fetcher.fetchAvailableLanguages();
161
+ if (langs && langs.length > 0) {
162
+ this.availableLanguages = langs;
163
+ }
164
+ return this.availableLanguages;
165
+ }
166
+ getLanguages() {
167
+ return this.availableLanguages;
168
+ }
169
+ getCurrentLocale() {
170
+ return this.currentLocale;
108
171
  }
109
172
  /**
110
173
  * Load translations for a specific locale.
111
- * Priority: Memory > Storage (Cache) > API
174
+ * Priority: Local Assets (Base) -> API (Override)
112
175
  */
113
176
  async loadLocale(locale) {
114
- const identifier = this.config.projectId || this.config.apiKey;
115
- if (this.translations[locale]) return;
116
- const cached = this.storage.get(`kopy_${identifier}_${locale}`);
117
- if (cached) {
177
+ if (this.translations[locale] && Object.keys(this.translations[locale]).length > 0) {
178
+ return;
179
+ }
180
+ this.translations[locale] = {};
181
+ try {
182
+ const localData = await this.fetcher.fetchLocal(locale);
183
+ if (localData && Object.keys(localData).length > 0) {
184
+ this.translations[locale] = { ...localData };
185
+ }
186
+ } catch (e) {
187
+ }
188
+ if (this.config.mode !== "local") {
118
189
  try {
119
- this.translations[locale] = JSON.parse(cached);
120
- return;
190
+ const remoteData = await this.fetcher.fetchTranslations(locale);
191
+ if (remoteData && Object.keys(remoteData).length > 0) {
192
+ this.translations[locale] = {
193
+ ...this.translations[locale],
194
+ ...remoteData
195
+ };
196
+ }
121
197
  } catch (e) {
122
- console.warn("[Kopynator] Failed to parse cached translations");
123
198
  }
124
199
  }
125
- const data = await this.fetcher.fetchTranslations(locale);
126
- this.translations[locale] = data;
127
- this.storage.set(`kopy_${identifier}_${locale}`, JSON.stringify(data));
128
- this.currentLocale = locale;
129
200
  }
130
201
  /**
131
202
  * Change current locale and load it if necessary.
@@ -139,7 +210,10 @@ var Kopynator = class {
139
210
  */
140
211
  translate(key, params = {}) {
141
212
  const localeTranslations = this.translations[this.currentLocale] || {};
142
- const template = localeTranslations[key] || key;
213
+ const template = localeTranslations[key];
214
+ if (!template) {
215
+ return key;
216
+ }
143
217
  return KopyCompiler.compile(template, params);
144
218
  }
145
219
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kopynator/core",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Core agnostic logic for Kopynator SDKs",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -19,7 +19,10 @@
19
19
  "build": "tsup src/index.ts --format cjs,esm --dts --clean",
20
20
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
21
21
  "lint": "eslint src/**/*.ts",
22
- "test": "jest"
22
+ "test": "jest",
23
+ "release:patch": "npm version patch",
24
+ "release:minor": "npm version minor",
25
+ "release:major": "npm version major"
23
26
  },
24
27
  "devDependencies": {
25
28
  "@types/jest": "^29.5.0",
@@ -30,5 +33,8 @@
30
33
  },
31
34
  "publishConfig": {
32
35
  "access": "public"
36
+ },
37
+ "dependencies": {
38
+ "@kopynator/core": "^1.0.1"
33
39
  }
34
40
  }