@kopynator/core 1.0.0

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/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # @kopynator/core
2
+
3
+ The agnostic core engine for the Kopynator i18n platform.
4
+
5
+ ## Features
6
+ - **Zero Dependencies**: Lightweight and fast.
7
+ - **ICU Pluralization**: `{n, plural, =0 {None} =1 {One} other {# items}}`.
8
+ - **Variable Interpolation**: `Hello {{name}}`.
9
+ - **Intelligent Caching**: Syncs with `localStorage` (Web) or uses Memory (Node).
10
+ - **Agnostic**: Works in Browser, React, Vue, Angular, Node.js, and more.
11
+
12
+ ## Installation
13
+ ```bash
14
+ npm install @kopynator/core
15
+ ```
16
+
17
+ ## Basic Usage
18
+ ```typescript
19
+ import { Kopynator } from '@kopynator/core';
20
+
21
+ const kopy = new Kopynator({
22
+ apiKey: 'YOUR_PROJECT_TOKEN',
23
+ projectId: 'YOUR_PROJECT_ID',
24
+ defaultLocale: 'en'
25
+ });
26
+
27
+ await kopy.init();
28
+
29
+ // Simple translation
30
+ console.log(kopy.t('welcome_message'));
31
+
32
+ // Interpolation
33
+ console.log(kopy.t('greeting', { name: 'Carlos' }));
34
+
35
+ // Pluralization
36
+ console.log(kopy.t('inbox_count', { n: 5 }));
37
+ ```
@@ -0,0 +1,73 @@
1
+ interface KopyConfig {
2
+ apiKey: string;
3
+ projectId?: string;
4
+ baseUrl?: string;
5
+ defaultLocale?: string;
6
+ mode?: 'local' | 'live';
7
+ }
8
+ interface TranslationResponse {
9
+ [key: string]: string;
10
+ }
11
+ declare class KopyFetcher {
12
+ private config;
13
+ private baseUrl;
14
+ constructor(config: KopyConfig);
15
+ fetchTranslations(locale: string): Promise<TranslationResponse>;
16
+ fetchAvailableLanguages(): Promise<string[]>;
17
+ }
18
+
19
+ declare class KopyCompiler {
20
+ /**
21
+ * Interpolates variables and handles pluralization in a string template.
22
+ * Interpolation: "Hello {{name}}" -> "Hello Carlos"
23
+ * Pluralization: "{n, plural, =0 {No items} =1 {One item} other {# items}}"
24
+ */
25
+ static compile(template: string, params?: Record<string, any>): string;
26
+ }
27
+
28
+ interface KopyStorage {
29
+ get(key: string): string | null;
30
+ set(key: string, value: string): void;
31
+ }
32
+ declare class MemoryStorage implements KopyStorage {
33
+ private cache;
34
+ get(key: string): string | null;
35
+ set(key: string, value: string): void;
36
+ }
37
+ declare class LocalStorageWrapper implements KopyStorage {
38
+ get(key: string): string | null;
39
+ set(key: string, value: string): void;
40
+ }
41
+ declare const getBestStorage: () => KopyStorage;
42
+
43
+ declare class Kopynator {
44
+ private config;
45
+ private fetcher;
46
+ private storage;
47
+ private translations;
48
+ private currentLocale;
49
+ constructor(config: KopyConfig);
50
+ /**
51
+ * Initialize the SDK and load initial translations.
52
+ */
53
+ init(): Promise<void>;
54
+ /**
55
+ * Load translations for a specific locale.
56
+ * Priority: Memory > Storage (Cache) > API
57
+ */
58
+ loadLocale(locale: string): Promise<void>;
59
+ /**
60
+ * Change current locale and load it if necessary.
61
+ */
62
+ setLocale(locale: string): Promise<void>;
63
+ /**
64
+ * Translate a key with optional parameters for interpolation.
65
+ */
66
+ translate(key: string, params?: Record<string, any>): string;
67
+ /**
68
+ * Shorthand for translate
69
+ */
70
+ t(key: string, params?: Record<string, any>): string;
71
+ }
72
+
73
+ export { KopyCompiler, type KopyConfig, KopyFetcher, type KopyStorage, Kopynator, LocalStorageWrapper, MemoryStorage, type TranslationResponse, getBestStorage };
@@ -0,0 +1,73 @@
1
+ interface KopyConfig {
2
+ apiKey: string;
3
+ projectId?: string;
4
+ baseUrl?: string;
5
+ defaultLocale?: string;
6
+ mode?: 'local' | 'live';
7
+ }
8
+ interface TranslationResponse {
9
+ [key: string]: string;
10
+ }
11
+ declare class KopyFetcher {
12
+ private config;
13
+ private baseUrl;
14
+ constructor(config: KopyConfig);
15
+ fetchTranslations(locale: string): Promise<TranslationResponse>;
16
+ fetchAvailableLanguages(): Promise<string[]>;
17
+ }
18
+
19
+ declare class KopyCompiler {
20
+ /**
21
+ * Interpolates variables and handles pluralization in a string template.
22
+ * Interpolation: "Hello {{name}}" -> "Hello Carlos"
23
+ * Pluralization: "{n, plural, =0 {No items} =1 {One item} other {# items}}"
24
+ */
25
+ static compile(template: string, params?: Record<string, any>): string;
26
+ }
27
+
28
+ interface KopyStorage {
29
+ get(key: string): string | null;
30
+ set(key: string, value: string): void;
31
+ }
32
+ declare class MemoryStorage implements KopyStorage {
33
+ private cache;
34
+ get(key: string): string | null;
35
+ set(key: string, value: string): void;
36
+ }
37
+ declare class LocalStorageWrapper implements KopyStorage {
38
+ get(key: string): string | null;
39
+ set(key: string, value: string): void;
40
+ }
41
+ declare const getBestStorage: () => KopyStorage;
42
+
43
+ declare class Kopynator {
44
+ private config;
45
+ private fetcher;
46
+ private storage;
47
+ private translations;
48
+ private currentLocale;
49
+ constructor(config: KopyConfig);
50
+ /**
51
+ * Initialize the SDK and load initial translations.
52
+ */
53
+ init(): Promise<void>;
54
+ /**
55
+ * Load translations for a specific locale.
56
+ * Priority: Memory > Storage (Cache) > API
57
+ */
58
+ loadLocale(locale: string): Promise<void>;
59
+ /**
60
+ * Change current locale and load it if necessary.
61
+ */
62
+ setLocale(locale: string): Promise<void>;
63
+ /**
64
+ * Translate a key with optional parameters for interpolation.
65
+ */
66
+ translate(key: string, params?: Record<string, any>): string;
67
+ /**
68
+ * Shorthand for translate
69
+ */
70
+ t(key: string, params?: Record<string, any>): string;
71
+ }
72
+
73
+ export { KopyCompiler, type KopyConfig, KopyFetcher, type KopyStorage, Kopynator, LocalStorageWrapper, MemoryStorage, type TranslationResponse, getBestStorage };
package/dist/index.js ADDED
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ KopyCompiler: () => KopyCompiler,
24
+ KopyFetcher: () => KopyFetcher,
25
+ Kopynator: () => Kopynator,
26
+ LocalStorageWrapper: () => LocalStorageWrapper,
27
+ MemoryStorage: () => MemoryStorage,
28
+ getBestStorage: () => getBestStorage
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+
32
+ // src/services/fetcher.ts
33
+ var KopyFetcher = class {
34
+ constructor(config) {
35
+ this.config = config;
36
+ this.baseUrl = config.baseUrl || "http://localhost:7300/api";
37
+ }
38
+ baseUrl;
39
+ async fetchTranslations(locale) {
40
+ try {
41
+ const url = `${this.baseUrl}/tokens/fetch?token=${this.config.apiKey}&lang=${locale}&nested=false&includeLangKey=false`;
42
+ const response = await fetch(url);
43
+ if (!response.ok) {
44
+ throw new Error(`Failed to fetch translations: ${response.statusText}`);
45
+ }
46
+ return await response.json();
47
+ } catch (error) {
48
+ console.error("[Kopynator] Error fetching translations:", error);
49
+ return {};
50
+ }
51
+ }
52
+ async fetchAvailableLanguages() {
53
+ try {
54
+ const url = `${this.baseUrl}/tokens/languages?token=${this.config.apiKey}`;
55
+ const response = await fetch(url);
56
+ if (!response.ok) {
57
+ throw new Error(`Failed to fetch languages: ${response.statusText}`);
58
+ }
59
+ return await response.json();
60
+ } catch (error) {
61
+ console.error("[Kopynator] Error fetching available languages:", error);
62
+ return [];
63
+ }
64
+ }
65
+ };
66
+
67
+ // src/utils/compiler.ts
68
+ var KopyCompiler = class {
69
+ /**
70
+ * Interpolates variables and handles pluralization in a string template.
71
+ * Interpolation: "Hello {{name}}" -> "Hello Carlos"
72
+ * Pluralization: "{n, plural, =0 {No items} =1 {One item} other {# items}}"
73
+ */
74
+ static compile(template, params = {}) {
75
+ if (!template) return "";
76
+ let compiled = template.replace(/\{(\w+),\s*plural,\s*((?:[^{}]*|\{[^{}]*\})*)\}/g, (match, key, rules) => {
77
+ const count = Number(params[key]);
78
+ if (isNaN(count)) return match;
79
+ const parts = rules.split(/\s*(=?\d+|other)\s*\{/);
80
+ const ruleMap = {};
81
+ for (let i = 1; i < parts.length; i += 2) {
82
+ const ruleKey = parts[i];
83
+ const ruleValue = parts[i + 1].split("}")[0];
84
+ ruleMap[ruleKey] = ruleValue;
85
+ }
86
+ const result = ruleMap[`=${count}`] || ruleMap[count.toString()] || ruleMap["other"] || "";
87
+ return result.replace("#", count.toString());
88
+ });
89
+ return compiled.replace(/\{\{\s*([\w.-]+)\s*\}\}/g, (match, key) => {
90
+ const value = params[key];
91
+ return value !== void 0 ? String(value) : match;
92
+ });
93
+ }
94
+ };
95
+
96
+ // src/services/storage.ts
97
+ var MemoryStorage = class {
98
+ cache = /* @__PURE__ */ new Map();
99
+ get(key) {
100
+ return this.cache.get(key) || null;
101
+ }
102
+ set(key, value) {
103
+ this.cache.set(key, value);
104
+ }
105
+ };
106
+ var LocalStorageWrapper = class {
107
+ get(key) {
108
+ return typeof localStorage !== "undefined" ? localStorage.getItem(key) : null;
109
+ }
110
+ set(key, value) {
111
+ if (typeof localStorage !== "undefined") localStorage.setItem(key, value);
112
+ }
113
+ };
114
+ var memoryStorage = new MemoryStorage();
115
+ var getBestStorage = () => {
116
+ if (typeof localStorage !== "undefined") {
117
+ return new LocalStorageWrapper();
118
+ }
119
+ return memoryStorage;
120
+ };
121
+
122
+ // src/index.ts
123
+ var Kopynator = class {
124
+ constructor(config) {
125
+ this.config = config;
126
+ this.fetcher = new KopyFetcher(config);
127
+ this.storage = getBestStorage();
128
+ this.currentLocale = config.defaultLocale || "en";
129
+ }
130
+ fetcher;
131
+ storage;
132
+ translations = {};
133
+ currentLocale;
134
+ /**
135
+ * Initialize the SDK and load initial translations.
136
+ */
137
+ async init() {
138
+ await this.loadLocale(this.currentLocale);
139
+ }
140
+ /**
141
+ * Load translations for a specific locale.
142
+ * Priority: Memory > Storage (Cache) > API
143
+ */
144
+ 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) {
149
+ try {
150
+ this.translations[locale] = JSON.parse(cached);
151
+ return;
152
+ } catch (e) {
153
+ console.warn("[Kopynator] Failed to parse cached translations");
154
+ }
155
+ }
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
+ }
161
+ /**
162
+ * Change current locale and load it if necessary.
163
+ */
164
+ async setLocale(locale) {
165
+ await this.loadLocale(locale);
166
+ this.currentLocale = locale;
167
+ }
168
+ /**
169
+ * Translate a key with optional parameters for interpolation.
170
+ */
171
+ translate(key, params = {}) {
172
+ const localeTranslations = this.translations[this.currentLocale] || {};
173
+ const template = localeTranslations[key] || key;
174
+ return KopyCompiler.compile(template, params);
175
+ }
176
+ /**
177
+ * Shorthand for translate
178
+ */
179
+ t(key, params = {}) {
180
+ return this.translate(key, params);
181
+ }
182
+ };
183
+ // Annotate the CommonJS export names for ESM import in node:
184
+ 0 && (module.exports = {
185
+ KopyCompiler,
186
+ KopyFetcher,
187
+ Kopynator,
188
+ LocalStorageWrapper,
189
+ MemoryStorage,
190
+ getBestStorage
191
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,159 @@
1
+ // src/services/fetcher.ts
2
+ var KopyFetcher = class {
3
+ constructor(config) {
4
+ this.config = config;
5
+ this.baseUrl = config.baseUrl || "http://localhost:7300/api";
6
+ }
7
+ baseUrl;
8
+ async fetchTranslations(locale) {
9
+ try {
10
+ const url = `${this.baseUrl}/tokens/fetch?token=${this.config.apiKey}&lang=${locale}&nested=false&includeLangKey=false`;
11
+ const response = await fetch(url);
12
+ if (!response.ok) {
13
+ throw new Error(`Failed to fetch translations: ${response.statusText}`);
14
+ }
15
+ return await response.json();
16
+ } catch (error) {
17
+ console.error("[Kopynator] Error fetching translations:", error);
18
+ return {};
19
+ }
20
+ }
21
+ async fetchAvailableLanguages() {
22
+ try {
23
+ const url = `${this.baseUrl}/tokens/languages?token=${this.config.apiKey}`;
24
+ const response = await fetch(url);
25
+ if (!response.ok) {
26
+ throw new Error(`Failed to fetch languages: ${response.statusText}`);
27
+ }
28
+ return await response.json();
29
+ } catch (error) {
30
+ console.error("[Kopynator] Error fetching available languages:", error);
31
+ return [];
32
+ }
33
+ }
34
+ };
35
+
36
+ // src/utils/compiler.ts
37
+ var KopyCompiler = class {
38
+ /**
39
+ * Interpolates variables and handles pluralization in a string template.
40
+ * Interpolation: "Hello {{name}}" -> "Hello Carlos"
41
+ * Pluralization: "{n, plural, =0 {No items} =1 {One item} other {# items}}"
42
+ */
43
+ static compile(template, params = {}) {
44
+ if (!template) return "";
45
+ let compiled = template.replace(/\{(\w+),\s*plural,\s*((?:[^{}]*|\{[^{}]*\})*)\}/g, (match, key, rules) => {
46
+ const count = Number(params[key]);
47
+ if (isNaN(count)) return match;
48
+ const parts = rules.split(/\s*(=?\d+|other)\s*\{/);
49
+ const ruleMap = {};
50
+ for (let i = 1; i < parts.length; i += 2) {
51
+ const ruleKey = parts[i];
52
+ const ruleValue = parts[i + 1].split("}")[0];
53
+ ruleMap[ruleKey] = ruleValue;
54
+ }
55
+ const result = ruleMap[`=${count}`] || ruleMap[count.toString()] || ruleMap["other"] || "";
56
+ return result.replace("#", count.toString());
57
+ });
58
+ return compiled.replace(/\{\{\s*([\w.-]+)\s*\}\}/g, (match, key) => {
59
+ const value = params[key];
60
+ return value !== void 0 ? String(value) : match;
61
+ });
62
+ }
63
+ };
64
+
65
+ // src/services/storage.ts
66
+ var MemoryStorage = class {
67
+ cache = /* @__PURE__ */ new Map();
68
+ get(key) {
69
+ return this.cache.get(key) || null;
70
+ }
71
+ set(key, value) {
72
+ this.cache.set(key, value);
73
+ }
74
+ };
75
+ var LocalStorageWrapper = class {
76
+ get(key) {
77
+ return typeof localStorage !== "undefined" ? localStorage.getItem(key) : null;
78
+ }
79
+ set(key, value) {
80
+ if (typeof localStorage !== "undefined") localStorage.setItem(key, value);
81
+ }
82
+ };
83
+ var memoryStorage = new MemoryStorage();
84
+ var getBestStorage = () => {
85
+ if (typeof localStorage !== "undefined") {
86
+ return new LocalStorageWrapper();
87
+ }
88
+ return memoryStorage;
89
+ };
90
+
91
+ // src/index.ts
92
+ var Kopynator = class {
93
+ constructor(config) {
94
+ this.config = config;
95
+ this.fetcher = new KopyFetcher(config);
96
+ this.storage = getBestStorage();
97
+ this.currentLocale = config.defaultLocale || "en";
98
+ }
99
+ fetcher;
100
+ storage;
101
+ translations = {};
102
+ currentLocale;
103
+ /**
104
+ * Initialize the SDK and load initial translations.
105
+ */
106
+ async init() {
107
+ await this.loadLocale(this.currentLocale);
108
+ }
109
+ /**
110
+ * Load translations for a specific locale.
111
+ * Priority: Memory > Storage (Cache) > API
112
+ */
113
+ 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) {
118
+ try {
119
+ this.translations[locale] = JSON.parse(cached);
120
+ return;
121
+ } catch (e) {
122
+ console.warn("[Kopynator] Failed to parse cached translations");
123
+ }
124
+ }
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
+ }
130
+ /**
131
+ * Change current locale and load it if necessary.
132
+ */
133
+ async setLocale(locale) {
134
+ await this.loadLocale(locale);
135
+ this.currentLocale = locale;
136
+ }
137
+ /**
138
+ * Translate a key with optional parameters for interpolation.
139
+ */
140
+ translate(key, params = {}) {
141
+ const localeTranslations = this.translations[this.currentLocale] || {};
142
+ const template = localeTranslations[key] || key;
143
+ return KopyCompiler.compile(template, params);
144
+ }
145
+ /**
146
+ * Shorthand for translate
147
+ */
148
+ t(key, params = {}) {
149
+ return this.translate(key, params);
150
+ }
151
+ };
152
+ export {
153
+ KopyCompiler,
154
+ KopyFetcher,
155
+ Kopynator,
156
+ LocalStorageWrapper,
157
+ MemoryStorage,
158
+ getBestStorage
159
+ };
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@kopynator/core",
3
+ "version": "1.0.0",
4
+ "description": "Core agnostic logic for Kopynator SDKs",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
20
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
21
+ "lint": "eslint src/**/*.ts",
22
+ "test": "jest"
23
+ },
24
+ "devDependencies": {
25
+ "@types/jest": "^29.5.0",
26
+ "jest": "^29.5.0",
27
+ "ts-jest": "^29.1.0",
28
+ "tsup": "^8.0.0",
29
+ "typescript": "^5.0.0"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ }
34
+ }