@voilabs/oilang 0.0.4 → 0.0.5

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 CHANGED
@@ -43,16 +43,17 @@ async function main() {
43
43
  await oilang.init();
44
44
 
45
45
  // Create a new locale
46
- await oilang.createLocale("en-US", "English (US)", "English");
46
+ await oilang.locales.create("en-US", "English (US)", "English");
47
47
 
48
48
  // Add a translation key
49
- await oilang.addTranslation("en-US", {
49
+ await oilang.translations.create("en-US", {
50
50
  key: "greeting",
51
51
  value: "Hello, World!",
52
52
  });
53
53
 
54
54
  // Retrieve translations
55
- const translations = await oilang.getAllTranslations("en-US");
55
+ // Example: Translations are stored in memory or Redis as a key-value pair.
56
+ const translations = await oilang.translations.list("en-US");
56
57
  console.log(translations);
57
58
  }
58
59
 
@@ -63,7 +64,7 @@ main();
63
64
 
64
65
  ### OILang
65
66
 
66
- The main entry point for the library. It orchestrates the interaction between the database adapter and the store.
67
+ The main entry point for the library. It orchestrates the interaction between the database adapter and the store, delegating specific operations to `locales` and `translations` namespaces.
67
68
 
68
69
  #### Constructor
69
70
 
@@ -78,21 +79,50 @@ new OILang(config: AdapterConfig)
78
79
  - **`init(): Promise<void>`**
79
80
  Connects to the database and initializes the store by loading existing locales and translations.
80
81
 
81
- - **`createLocale(locale: string, nativeName: string, englishName: string): Promise<Result<Locale>>`**
82
+ #### Namespaces
83
+
84
+ - **`oilang.locales`**: Manages locale operations.
85
+ - **`oilang.translations`**: Manages translation operations.
86
+
87
+ ### Locales
88
+
89
+ Access via `oilang.locales`.
90
+
91
+ #### Methods
92
+
93
+ - **`list(): Promise<ActionResponse<LocaleData[]>>`**
94
+ Retrieves all available locales from the store.
95
+
96
+ - **`create(locale: string, nativeName: string, englishName: string): Promise<ActionResponse<LocaleData>>`**
82
97
  Creates a new locale in the database and updates the store.
83
98
 
84
- - **`deleteLocale(locale: string): Promise<Result<Locale>>`**
99
+ - **`delete(locale: string): Promise<ActionResponse<LocaleData>>`**
85
100
  Deletes a locale and its associated translations from both the database and the store.
86
101
 
87
- - **`addTranslation(locale: string, config: { key: string; value: string }): Promise<Result<Translation>>`**
88
- Adds a translation key-value pair for a specific locale.
102
+ - **`update(locale: string, nativeName: string, englishName: string): Promise<ActionResponse<LocaleData>>`**
103
+ Updates locale information in the database and store.
89
104
 
90
- - **`getAllLocales(): Promise<Result<Locale[]>>`**
91
- Retrieves all available locales from the store.
105
+ ### Translations
106
+
107
+ Access via `oilang.translations`.
92
108
 
93
- - **`getAllTranslations(locale: string): Promise<Record<string, string>>`**
109
+ #### Methods
110
+
111
+ - **`list(locale: string): Promise<Record<string, string>>`**
94
112
  Retrieves all translations for a specific locale from the store. Returns a key-value map.
95
113
 
114
+ - **`create(locale: string, config: { key: string; value: string }): Promise<ActionResponse<TranslationData>>`**
115
+ Adds a translation key-value pair for a specific locale.
116
+
117
+ - **`update(locale: string, key: string, newValue: string): Promise<ActionResponse<TranslationData>>`**
118
+ Updates an existing translation value.
119
+
120
+ - **`delete(locale: string, key: string): Promise<ActionResponse<TranslationData>>`**
121
+ Deletes a translation key.
122
+
123
+ - **`translate(locale: string, key: string, variables?: Record<string, string | number>): Promise<string>`**
124
+ Retrieves a single translation, optionally performing variable interpolation. Uses fallback locale if translation is missing.
125
+
96
126
  ### Adapters
97
127
 
98
128
  #### PostgreSQL
@@ -124,7 +154,7 @@ new MemoryStore();
124
154
 
125
155
  #### RedisStore
126
156
 
127
- Stores data in a Redis instance. essential for distributed applications or when data persistence across restarts (without DB reload) is desired.
157
+ Stores data in a Redis instance. Essential for distributed applications or when data persistence across restarts (without DB reload) is desired.
128
158
 
129
159
  **Constructor**
130
160
 
@@ -163,5 +193,7 @@ Stores the translation strings.
163
193
  Most methods return a result object pattern to handle errors gracefully without throwing.
164
194
 
165
195
  ```typescript
166
- type Result<T> = { error: null; data: T } | { error: Error; data: null };
196
+ type ActionResponse<T> =
197
+ | { error: Error & { code?: string }; data: null }
198
+ | { error: null; data: T };
167
199
  ```
@@ -1,136 +1,154 @@
1
1
  import { Client } from "pg";
2
- export class PostgreSQL {
3
- config;
2
+ class Locales {
3
+ schema;
4
4
  client;
5
- schemaNames;
6
- constructor(config, customizationConfig) {
7
- this.config = config;
8
- this.client = new Client(config);
9
- this.schemaNames = customizationConfig.schemaNames ?? {
10
- keys: "keys",
11
- locales: "locales",
12
- };
5
+ constructor(schema, client) {
6
+ this.schema = schema;
7
+ this.client = client;
13
8
  }
14
- async connect() {
15
- await this.client.connect();
16
- const QUERY = [
17
- `CREATE SCHEMA IF NOT EXISTS ${this.schemaNames.locales};`,
18
- `CREATE SCHEMA IF NOT EXISTS ${this.schemaNames.keys};`,
19
- `
20
- CREATE TABLE IF NOT EXISTS ${this.schemaNames.locales} (
21
- code VARCHAR(10) PRIMARY KEY,
22
- native_name VARCHAR(255) NOT NULL,
23
- english_name VARCHAR(255) NOT NULL
24
- );
25
- `,
26
- `
27
- CREATE TABLE IF NOT EXISTS ${this.schemaNames.keys} (
28
- key VARCHAR(255) NOT NULL,
29
- value TEXT NOT NULL,
30
- locale_id VARCHAR(10) NOT NULL,
31
- FOREIGN KEY (locale_id) REFERENCES ${this.schemaNames.locales}(code)
32
- );
33
- `,
34
- ];
35
- await this.client.query(QUERY.join("\n"));
36
- }
37
- getSchemaNames() {
38
- return this.schemaNames;
9
+ async list() {
10
+ try {
11
+ const result = await this.client.query(`SELECT * FROM ${this.schema}`);
12
+ return { success: true, data: result.rows };
13
+ }
14
+ catch (error) {
15
+ return { success: false, error };
16
+ }
39
17
  }
40
- async addLocale(locale, nativeName, englishName) {
18
+ async create(locale, nativeName, englishName) {
41
19
  try {
42
- const existingLocale = await this.client.query(`SELECT * FROM ${this.schemaNames.locales} WHERE code = $1`, [locale]);
43
- if (existingLocale.rows.length > 0) {
20
+ const existing = await this.client.query(`SELECT * FROM ${this.schema} WHERE code = $1`, [locale]);
21
+ if (existing.rows.length > 0) {
44
22
  const error = new Error("Locale already exists");
45
23
  error.code = "LOCALE_ALREADY_EXISTS";
46
24
  throw error;
47
25
  }
48
- const result = await this.client.query(`INSERT INTO ${this.schemaNames.locales} (code, native_name, english_name) VALUES ($1, $2, $3) RETURNING *`, [locale, nativeName, englishName]);
49
- return {
50
- success: true,
51
- data: result.rows.at(0),
52
- };
26
+ const result = await this.client.query(`INSERT INTO ${this.schema} (code, native_name, english_name) VALUES ($1, $2, $3) RETURNING *`, [locale, nativeName, englishName]);
27
+ return { success: true, data: result.rows[0] };
53
28
  }
54
29
  catch (error) {
55
- return {
56
- success: false,
57
- error: error,
58
- };
30
+ return { success: false, error };
59
31
  }
60
32
  }
61
- async getAllLocales() {
33
+ async delete(locale) {
62
34
  try {
63
- const result = await this.client.query(`SELECT * FROM ${this.schemaNames.locales}`);
64
- return {
65
- success: true,
66
- data: result.rows,
67
- };
35
+ await this.client.query(`DELETE FROM ${this.schema} WHERE code = $1`, [locale]);
36
+ return { success: true, data: { code: locale } };
68
37
  }
69
38
  catch (error) {
70
- return {
71
- success: false,
72
- error: error,
73
- };
39
+ return { success: false, error };
74
40
  }
75
41
  }
76
- async getAllTranslations() {
42
+ async update(locale, nativeName, englishName) {
77
43
  try {
78
- const result = await this.client.query(`SELECT * FROM ${this.schemaNames.keys}`);
79
- return {
80
- success: true,
81
- data: result.rows,
82
- };
44
+ const result = await this.client.query(`UPDATE ${this.schema} SET native_name = $2, english_name = $3 WHERE code = $1 RETURNING *`, [locale, nativeName, englishName]);
45
+ if (result.rows.length === 0) {
46
+ const error = new Error("Locale does not exist");
47
+ error.code = "LOCALE_NOT_FOUND";
48
+ throw error;
49
+ }
50
+ return { success: true, data: result.rows[0] };
83
51
  }
84
52
  catch (error) {
85
- return {
86
- success: false,
87
- error: error,
88
- };
53
+ return { success: false, error: error };
89
54
  }
90
55
  }
91
- async deleteLocale(locale) {
56
+ }
57
+ class Translations {
58
+ schema;
59
+ client;
60
+ localesSchema;
61
+ constructor(schema, client, localesSchema) {
62
+ this.schema = schema;
63
+ this.client = client;
64
+ this.localesSchema = localesSchema;
65
+ }
66
+ async list() {
92
67
  try {
93
- await this.client.query(`DELETE FROM ${this.schemaNames.locales} WHERE code = $1`, [locale]);
94
- await this.client.query(`DELETE FROM ${this.schemaNames.keys} WHERE locale_id = $1`, [locale]);
95
- return {
96
- success: true,
97
- data: {
98
- code: locale,
99
- },
100
- };
68
+ const result = await this.client.query(`SELECT * FROM ${this.schema}`);
69
+ return { success: true, data: result.rows };
101
70
  }
102
71
  catch (error) {
103
- return {
104
- success: false,
105
- error: error,
106
- };
72
+ return { success: false, error };
107
73
  }
108
74
  }
109
- async addTranslation(locale, key, value) {
75
+ async create(key, value, locale) {
110
76
  try {
111
- const existingTranslation = await this.client.query(`SELECT * FROM ${this.schemaNames.keys} WHERE locale_id = $1 AND key = $2`, [locale, key]);
112
- if (existingTranslation.rows.length > 0) {
113
- const error = new Error("Translation already exists");
114
- error.code = "TRANSLATION_ALREADY_EXISTS";
115
- throw error;
116
- }
117
- const existingLocale = await this.client.query(`SELECT * FROM ${this.schemaNames.locales} WHERE code = $1`, [locale]);
118
- if (existingLocale.rows.length === 0) {
77
+ const localeCheck = await this.client.query(`SELECT * FROM ${this.localesSchema} WHERE code = $1`, [locale]);
78
+ if (localeCheck.rows.length === 0) {
119
79
  const error = new Error("Locale does not exist");
120
80
  error.code = "LOCALE_NOT_FOUND";
121
81
  throw error;
122
82
  }
123
- const result = await this.client.query(`INSERT INTO ${this.schemaNames.keys} (locale_id, key, value) VALUES ($1, $2, $3) RETURNING *`, [locale, key, value]);
124
- return {
125
- success: true,
126
- data: result.rows.at(0),
127
- };
83
+ const result = await this.client.query(`INSERT INTO ${this.schema} (key, value, locale_id) VALUES ($1, $2, $3) RETURNING *`, [key, value, locale]);
84
+ return { success: true, data: result.rows[0] };
128
85
  }
129
86
  catch (error) {
130
- return {
131
- success: false,
132
- error: error,
133
- };
87
+ return { success: false, error: error };
134
88
  }
135
89
  }
90
+ async delete(key, locale) {
91
+ try {
92
+ await this.client.query(`DELETE FROM ${this.schema} WHERE key = $1 AND locale_id = $2`, [key, locale]);
93
+ return { success: true, data: { locale_id: locale, key } };
94
+ }
95
+ catch (error) {
96
+ return { success: false, error };
97
+ }
98
+ }
99
+ async update(key, value, locale) {
100
+ try {
101
+ const result = await this.client.query(`UPDATE ${this.schema} SET value = $2 WHERE key = $1 AND locale_id = $3 RETURNING *`, [key, value, locale]);
102
+ if (result.rows.length === 0) {
103
+ const error = new Error("Translation does not exist");
104
+ error.code = "TRANSLATION_NOT_FOUND";
105
+ throw error;
106
+ }
107
+ return { success: true, data: result.rows[0] };
108
+ }
109
+ catch (error) {
110
+ return { success: false, error: error };
111
+ }
112
+ }
113
+ }
114
+ export class PostgreSQL {
115
+ config;
116
+ client;
117
+ schemaNames;
118
+ locales;
119
+ translations;
120
+ constructor(config, customizationConfig) {
121
+ this.config = config;
122
+ this.client = new Client(config);
123
+ this.schemaNames = customizationConfig.schemaNames ?? {
124
+ keys: "keys",
125
+ locales: "locales",
126
+ };
127
+ this.locales = new Locales(this.schemaNames.locales, this.client);
128
+ this.translations = new Translations(this.schemaNames.keys, this.client, this.schemaNames.locales);
129
+ }
130
+ async connect() {
131
+ await this.client.connect();
132
+ const QUERY = `
133
+ CREATE TABLE IF NOT EXISTS ${this.schemaNames.locales} (
134
+ code VARCHAR(10) PRIMARY KEY,
135
+ native_name VARCHAR(255) NOT NULL,
136
+ english_name VARCHAR(255) NOT NULL,
137
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
138
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
139
+ );
140
+
141
+ CREATE TABLE IF NOT EXISTS ${this.schemaNames.keys} (
142
+ key VARCHAR(255) NOT NULL,
143
+ value TEXT NOT NULL,
144
+ locale_id VARCHAR(10) NOT NULL,
145
+ PRIMARY KEY (key, locale_id),
146
+ FOREIGN KEY (locale_id) REFERENCES ${this.schemaNames.locales}(code) ON DELETE CASCADE
147
+ );
148
+ `;
149
+ await this.client.query(QUERY);
150
+ }
151
+ getSchemaNames() {
152
+ return this.schemaNames;
153
+ }
136
154
  }
package/dist/oilang.js CHANGED
@@ -1,43 +1,21 @@
1
- export class OILang {
2
- config;
1
+ class Locale {
3
2
  database;
4
3
  store;
5
- constructor(config) {
6
- this.config = config;
7
- this.database = config.database;
8
- this.store = config.store;
4
+ constructor(database, store) {
5
+ this.database = database;
6
+ this.store = store;
9
7
  }
10
- async init() {
11
- await this.database.connect();
12
- const [locales, translations] = await Promise.all([
13
- this.database.getAllLocales(),
14
- this.database.getAllTranslations(),
15
- ]);
16
- if (locales.success && translations.success) {
17
- const loadableLocales = locales.data.map((l) => ({
18
- code: l.code,
19
- native_name: l.native_name,
20
- english_name: l.english_name,
21
- created_at: l.created_at,
22
- updated_at: l.updated_at,
23
- }));
24
- const loadableTranslations = loadableLocales.reduce((acc, l) => {
25
- acc[l.code] = translations.data
26
- .filter((t) => t.locale_id === l.code)
27
- .reduce((acc, t) => {
28
- acc[t.key] = t.value;
29
- return acc;
30
- }, {});
31
- return acc;
32
- }, {});
33
- await this.store.load(loadableLocales, loadableTranslations);
34
- }
35
- else {
36
- throw new Error("Failed to load locales or translations");
37
- }
8
+ async list() {
9
+ const response = await this.store.getAll({
10
+ seed: "locales",
11
+ });
12
+ return {
13
+ error: null,
14
+ data: response,
15
+ };
38
16
  }
39
- async createLocale(locale, nativeName, englishName) {
40
- const response = await this.database.addLocale(locale, nativeName, englishName);
17
+ async create(locale, nativeName, englishName) {
18
+ const response = await this.database.locales.create(locale, nativeName, englishName);
41
19
  if (response.success) {
42
20
  this.store.set({
43
21
  seed: "locales",
@@ -55,8 +33,8 @@ export class OILang {
55
33
  };
56
34
  }
57
35
  }
58
- async deleteLocale(locale) {
59
- const response = await this.database.deleteLocale(locale);
36
+ async delete(locale) {
37
+ const response = await this.database.locales.delete(locale);
60
38
  if (response.success) {
61
39
  this.store.remove({
62
40
  seed: "locales",
@@ -74,24 +52,48 @@ export class OILang {
74
52
  };
75
53
  }
76
54
  }
77
- async getAllLocales() {
78
- const response = await this.store.getAll({
79
- seed: "locales",
80
- });
81
- return {
82
- error: null,
83
- data: response,
84
- };
55
+ async update(locale, nativeName, englishName) {
56
+ const response = await this.database.locales.update(locale, nativeName, englishName);
57
+ if (response.success) {
58
+ this.store.update({
59
+ seed: "locales",
60
+ code: locale,
61
+ locale: {
62
+ native_name: nativeName,
63
+ english_name: englishName,
64
+ },
65
+ });
66
+ return {
67
+ error: null,
68
+ data: response.data,
69
+ };
70
+ }
71
+ else {
72
+ return {
73
+ error: response.error,
74
+ data: null,
75
+ };
76
+ }
85
77
  }
86
- async getAllTranslations(locale) {
78
+ }
79
+ class Translation {
80
+ database;
81
+ store;
82
+ fallbackLocale;
83
+ constructor(database, store, fallbackLocale) {
84
+ this.database = database;
85
+ this.store = store;
86
+ this.fallbackLocale = fallbackLocale;
87
+ }
88
+ async list(locale) {
87
89
  const response = await this.store.getAll({
88
90
  seed: "translations",
89
91
  locale,
90
92
  });
91
93
  return response;
92
94
  }
93
- async addTranslation(locale, config) {
94
- const response = await this.database.addTranslation(locale, config.key, config.value);
95
+ async create(locale, config) {
96
+ const response = await this.database.translations.create(config.key, config.value, locale);
95
97
  if (response.success) {
96
98
  this.store.set({
97
99
  seed: "translations",
@@ -111,4 +113,112 @@ export class OILang {
111
113
  };
112
114
  }
113
115
  }
116
+ async update(locale, key, newValue) {
117
+ const response = await this.database.translations.update(key, newValue, locale);
118
+ if (response.success) {
119
+ this.store.update({
120
+ seed: "translations",
121
+ locale,
122
+ key,
123
+ value: newValue,
124
+ });
125
+ return {
126
+ error: null,
127
+ data: response.data,
128
+ };
129
+ }
130
+ else {
131
+ return {
132
+ error: response.error,
133
+ data: null,
134
+ };
135
+ }
136
+ }
137
+ async delete(locale, key) {
138
+ const response = await this.database.translations.delete(key, locale);
139
+ if (response.success) {
140
+ this.store.remove({
141
+ seed: "translations",
142
+ locale,
143
+ key,
144
+ });
145
+ return {
146
+ error: null,
147
+ data: response.data,
148
+ };
149
+ }
150
+ else {
151
+ return {
152
+ error: response.error,
153
+ data: null,
154
+ };
155
+ }
156
+ }
157
+ async translate(locale, key, variables) {
158
+ let translation = await this.store.get({
159
+ seed: "translations",
160
+ locale,
161
+ key,
162
+ });
163
+ if (!translation && this.fallbackLocale) {
164
+ translation = await this.store.get({
165
+ seed: "translations",
166
+ locale: this.fallbackLocale,
167
+ key,
168
+ });
169
+ }
170
+ if (!translation)
171
+ return key;
172
+ if (variables) {
173
+ for (const [varKey, varValue] of Object.entries(variables)) {
174
+ translation = translation.replace(new RegExp(`{{${varKey}}}`, "g"), String(varValue));
175
+ }
176
+ }
177
+ return translation;
178
+ }
179
+ }
180
+ export class OILang {
181
+ config;
182
+ database;
183
+ store;
184
+ fallbackLocale;
185
+ locales;
186
+ translations;
187
+ constructor(config) {
188
+ this.config = config;
189
+ this.database = config.database;
190
+ this.store = config.store;
191
+ this.fallbackLocale = config.fallbackLocale ?? "en-US";
192
+ this.locales = new Locale(this.database, this.store);
193
+ this.translations = new Translation(this.database, this.store, this.fallbackLocale);
194
+ }
195
+ async init() {
196
+ await this.database.connect();
197
+ const [locales, translations] = await Promise.all([
198
+ this.database.locales.list(),
199
+ this.database.translations.list(),
200
+ ]);
201
+ if (locales.success && translations.success) {
202
+ const loadableLocales = locales.data.map((l) => ({
203
+ code: l.code,
204
+ native_name: l.native_name,
205
+ english_name: l.english_name,
206
+ created_at: l.created_at,
207
+ updated_at: l.updated_at,
208
+ }));
209
+ const loadableTranslations = loadableLocales.reduce((acc, l) => {
210
+ acc[l.code] = translations.data
211
+ .filter((t) => t.locale_id === l.code)
212
+ .reduce((accData, t) => {
213
+ accData[t.key] = t.value;
214
+ return accData;
215
+ }, {});
216
+ return acc;
217
+ }, {});
218
+ await this.store.load(loadableLocales, loadableTranslations);
219
+ }
220
+ else {
221
+ throw new Error("Failed to load locales or translations");
222
+ }
223
+ }
114
224
  }
package/dist/types.js ADDED
File without changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@voilabs/oilang",
3
3
  "description": "A robust internationalization (i18n) handling library designed for performance and flexibility.",
4
- "version": "0.0.4",
4
+ "version": "0.0.5",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {