transduck 0.5.3 → 0.6.1

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.js CHANGED
@@ -1,14 +1,18 @@
1
1
  import { createHash } from 'crypto';
2
2
  import { loadConfig } from './config.js';
3
3
  import { TranslationStore } from './storage.js';
4
- import { translate as backendTranslate, translatePlural as backendTranslatePlural } from './backend.js';
4
+ import { SharedStore } from './shared-store.js';
5
+ import { TranslationResult } from './result.js';
6
+ import { translate as backendTranslate, translatePlural as backendTranslatePlural, detectLanguage as backendDetectLanguage } from './backend.js';
5
7
  import { validateTranslation, extractPlaceholders } from './validation.js';
6
8
  import { getPluralCategory, getPluralCategories, interpolateVars } from './plural.js';
7
9
  let state = {
8
10
  config: null,
9
11
  store: null,
12
+ sharedStore: null,
10
13
  targetLang: null,
11
14
  pendingTranslations: new Map(),
15
+ backgroundKeys: new Set(),
12
16
  };
13
17
  function hash(text) {
14
18
  return createHash('sha256').update(text).digest('hex');
@@ -19,85 +23,156 @@ export async function initialize(config) {
19
23
  await store.initialize();
20
24
  state.config = cfg;
21
25
  state.store = store;
26
+ if (cfg.sharedUrl) {
27
+ try {
28
+ const shared = new SharedStore(cfg.sharedUrl);
29
+ await shared.initialize();
30
+ state.sharedStore = shared;
31
+ }
32
+ catch (err) {
33
+ console.warn(`[transduck] Could not connect to shared store: ${err.message}`);
34
+ state.sharedStore = null;
35
+ }
36
+ }
22
37
  }
23
38
  export function setLanguage(lang) {
24
39
  state.targetLang = lang.toUpperCase();
25
40
  }
26
41
  export function _resetState() {
27
- state = { config: null, store: null, targetLang: null, pendingTranslations: new Map() };
42
+ state = {
43
+ config: null, store: null, sharedStore: null, targetLang: null,
44
+ pendingTranslations: new Map(), backgroundKeys: new Set(),
45
+ };
28
46
  }
29
47
  export function _getStore() {
30
48
  return state.store;
31
49
  }
32
- export async function ait(sourceText, context, vars) {
50
+ export function _getSharedStore() {
51
+ return state.sharedStore;
52
+ }
53
+ export function _setSharedStore(shared) {
54
+ state.sharedStore = shared;
55
+ }
56
+ export async function ait(sourceText, contextOrOpts, vars) {
33
57
  if (!state.config || !state.store) {
34
58
  throw new Error('transduck not initialized. Call initialize() first.');
35
59
  }
36
60
  if (!state.targetLang) {
37
61
  throw new Error('Target language not set. Call setLanguage() first.');
38
62
  }
63
+ // Parse overloaded args: ait(text, context?, vars?) OR ait(text, opts)
64
+ let context;
65
+ let resolvedVars;
66
+ let sourceLang;
67
+ let background = false;
68
+ if (typeof contextOrOpts === 'string') {
69
+ context = contextOrOpts;
70
+ resolvedVars = vars;
71
+ }
72
+ else if (contextOrOpts != null) {
73
+ context = contextOrOpts.context;
74
+ resolvedVars = contextOrOpts.vars;
75
+ sourceLang = contextOrOpts.sourceLang;
76
+ background = contextOrOpts.background ?? false;
77
+ }
78
+ else {
79
+ resolvedVars = vars;
80
+ }
39
81
  const cfg = state.config;
40
- if (state.targetLang === cfg.sourceLang) {
41
- return interpolateVars(sourceText, vars);
82
+ const effectiveSourceLang = sourceLang?.toUpperCase() ?? cfg.sourceLang;
83
+ const targetLang = state.targetLang;
84
+ if (targetLang === effectiveSourceLang) {
85
+ return new TranslationResult(interpolateVars(sourceText, resolvedVars), { pending: false, sourceLang: effectiveSourceLang, lang: targetLang });
42
86
  }
43
87
  const projectContextHash = hash(cfg.projectContext);
44
88
  const stringContextHash = hash(context ?? '');
45
- // Cache lookup
46
- const cached = await state.store.lookup({
47
- sourceText, sourceLang: cfg.sourceLang, targetLang: state.targetLang,
89
+ const lookupParams = {
90
+ sourceText, sourceLang: effectiveSourceLang, targetLang,
48
91
  projectContextHash, stringContextHash,
49
- });
50
- if (cached !== null)
51
- return interpolateVars(cached, vars);
92
+ };
93
+ // Tier 1: local SQLite lookup
94
+ const cached = await state.store.lookup(lookupParams);
95
+ if (cached !== null) {
96
+ return new TranslationResult(interpolateVars(cached, resolvedVars), { pending: false, sourceLang: effectiveSourceLang, lang: targetLang });
97
+ }
98
+ // Tier 2: shared Postgres lookup
99
+ if (state.sharedStore) {
100
+ const sharedCached = await state.sharedStore.lookup(lookupParams);
101
+ if (sharedCached !== null) {
102
+ // Propagate to local store
103
+ await state.store.insert({
104
+ ...lookupParams, stringContext: context ?? '',
105
+ translatedText: sharedCached, model: cfg.backendModel, status: 'translated',
106
+ });
107
+ return new TranslationResult(interpolateVars(sharedCached, resolvedVars), { pending: false, sourceLang: effectiveSourceLang, lang: targetLang });
108
+ }
109
+ }
52
110
  // Read-only mode: skip backend, return source text
53
111
  if (cfg.readOnly) {
54
- return interpolateVars(sourceText, vars);
112
+ return new TranslationResult(interpolateVars(sourceText, resolvedVars), { pending: false, sourceLang: effectiveSourceLang, lang: targetLang });
113
+ }
114
+ // Background mode: return immediately with pending=true
115
+ if (background) {
116
+ const bgKey = `${sourceText}|${effectiveSourceLang}|${targetLang}|${projectContextHash}|${stringContextHash}`;
117
+ if (!state.backgroundKeys.has(bgKey)) {
118
+ state.backgroundKeys.add(bgKey);
119
+ _doTranslate(sourceText, effectiveSourceLang, targetLang, projectContextHash, stringContextHash, context ?? '', cfg)
120
+ .finally(() => state.backgroundKeys.delete(bgKey));
121
+ }
122
+ return new TranslationResult(interpolateVars(sourceText, resolvedVars), { pending: true, sourceLang: effectiveSourceLang, lang: targetLang });
55
123
  }
56
124
  // In-process dedup
57
- const lockKey = `${sourceText}|${cfg.sourceLang}|${state.targetLang}|${projectContextHash}|${stringContextHash}`;
125
+ const lockKey = `${sourceText}|${effectiveSourceLang}|${targetLang}|${projectContextHash}|${stringContextHash}`;
58
126
  if (state.pendingTranslations.has(lockKey)) {
59
127
  const pending = await state.pendingTranslations.get(lockKey);
60
- return interpolateVars(pending, vars);
128
+ return new TranslationResult(interpolateVars(pending, resolvedVars), { pending: false, sourceLang: effectiveSourceLang, lang: targetLang });
61
129
  }
62
- const translationPromise = (async () => {
63
- // Double-check after getting in
64
- const rechecked = await state.store.lookup({
65
- sourceText, sourceLang: cfg.sourceLang, targetLang: state.targetLang,
66
- projectContextHash, stringContextHash,
67
- });
68
- if (rechecked !== null)
69
- return rechecked;
70
- try {
71
- const translated = await backendTranslate(sourceText, cfg.sourceLang, state.targetLang, cfg.projectContext, context ?? null, cfg);
72
- if (!validateTranslation(sourceText, translated)) {
73
- console.warn(`[transduck] Validation failed for: ${sourceText} -> ${translated}`);
74
- await state.store.insert({
75
- sourceText, sourceLang: cfg.sourceLang, targetLang: state.targetLang,
76
- projectContextHash, stringContextHash, stringContext: context ?? '',
77
- translatedText: translated, model: cfg.backendModel, status: 'failed',
78
- });
79
- return sourceText;
80
- }
81
- await state.store.insert({
82
- sourceText, sourceLang: cfg.sourceLang, targetLang: state.targetLang,
83
- projectContextHash, stringContextHash, stringContext: context ?? '',
84
- translatedText: translated, model: cfg.backendModel, status: 'translated',
85
- });
86
- return translated;
87
- }
88
- catch (err) {
89
- console.warn(`[transduck] Backend failed for: ${sourceText}`, err);
90
- return sourceText;
91
- }
92
- finally {
93
- state.pendingTranslations.delete(lockKey);
94
- }
95
- })();
130
+ const translationPromise = _doTranslate(sourceText, effectiveSourceLang, targetLang, projectContextHash, stringContextHash, context ?? '', cfg);
96
131
  state.pendingTranslations.set(lockKey, translationPromise);
97
132
  const result = await translationPromise;
98
- return interpolateVars(result, vars);
133
+ state.pendingTranslations.delete(lockKey);
134
+ return new TranslationResult(interpolateVars(result, resolvedVars), { pending: false, sourceLang: effectiveSourceLang, lang: targetLang });
135
+ }
136
+ async function _doTranslate(sourceText, sourceLang, targetLang, projectContextHash, stringContextHash, stringContext, cfg) {
137
+ // Double-check local cache
138
+ const rechecked = await state.store.lookup({
139
+ sourceText, sourceLang, targetLang, projectContextHash, stringContextHash,
140
+ });
141
+ if (rechecked !== null)
142
+ return rechecked;
143
+ try {
144
+ const translated = await backendTranslate(sourceText, sourceLang, targetLang, cfg.projectContext, stringContext || null, cfg);
145
+ const insertParams = {
146
+ sourceText, sourceLang, targetLang,
147
+ projectContextHash, stringContextHash, stringContext,
148
+ translatedText: translated, model: cfg.backendModel,
149
+ };
150
+ if (!validateTranslation(sourceText, translated)) {
151
+ console.warn(`[transduck] Validation failed for: ${sourceText} -> ${translated}`);
152
+ await state.store.insert({ ...insertParams, status: 'failed' });
153
+ if (state.sharedStore)
154
+ await state.sharedStore.insert({ ...insertParams, status: 'failed' });
155
+ return sourceText;
156
+ }
157
+ await state.store.insert({ ...insertParams, status: 'translated' });
158
+ if (state.sharedStore)
159
+ await state.sharedStore.insert({ ...insertParams, status: 'translated' });
160
+ return translated;
161
+ }
162
+ catch (err) {
163
+ console.warn(`[transduck] Backend failed for: ${sourceText}`, err);
164
+ return sourceText;
165
+ }
99
166
  }
100
167
  export { createTransDuckHandler } from './handler.js';
168
+ export { TranslationResult } from './result.js';
169
+ export { SharedStore } from './shared-store.js';
170
+ export async function detectLanguage(text) {
171
+ if (!state.config) {
172
+ throw new Error('transduck not initialized. Call initialize() first.');
173
+ }
174
+ return backendDetectLanguage(text, state.config);
175
+ }
101
176
  export async function aitPlural(one, other, count, opts) {
102
177
  if (!state.config || !state.store) {
103
178
  throw new Error('transduck not initialized. Call initialize() first.');
@@ -107,6 +182,8 @@ export async function aitPlural(one, other, count, opts) {
107
182
  }
108
183
  const cfg = state.config;
109
184
  const context = opts?.context;
185
+ const effectiveSourceLang = opts?.sourceLang?.toUpperCase() ?? cfg.sourceLang;
186
+ const background = opts?.background ?? false;
110
187
  // Build vars with count
111
188
  let vars;
112
189
  if (!opts?.vars) {
@@ -118,37 +195,76 @@ export async function aitPlural(one, other, count, opts) {
118
195
  else {
119
196
  vars = { ...opts.vars };
120
197
  }
198
+ const targetLang = state.targetLang;
121
199
  // Same language, 2-form language: select directly from provided forms
122
- if (state.targetLang === cfg.sourceLang) {
123
- const categories = getPluralCategories(cfg.sourceLang);
200
+ if (targetLang === effectiveSourceLang) {
201
+ const categories = getPluralCategories(effectiveSourceLang);
124
202
  if (categories.size <= 2) {
125
- const category = getPluralCategory(cfg.sourceLang, count);
203
+ const category = getPluralCategory(effectiveSourceLang, count);
126
204
  const form = category === 'one' ? one : other;
127
- return interpolateVars(form, vars);
205
+ return new TranslationResult(interpolateVars(form, vars), { pending: false, sourceLang: effectiveSourceLang, lang: targetLang });
128
206
  }
129
207
  }
130
208
  // Build cache key
131
209
  const sourceKey = one + '\x00' + other;
132
210
  const projectContextHash = hash(cfg.projectContext);
133
211
  const stringContextHash = hash(context ?? '');
134
- // Cache lookup
135
- const cached = await state.store.lookupPlural({
136
- sourceText: sourceKey, sourceLang: cfg.sourceLang, targetLang: state.targetLang,
212
+ const lookupParams = {
213
+ sourceText: sourceKey, sourceLang: effectiveSourceLang, targetLang,
137
214
  projectContextHash, stringContextHash,
138
- });
139
- const category = getPluralCategory(state.targetLang, count);
215
+ };
216
+ const category = getPluralCategory(targetLang, count);
217
+ // Tier 1: local cache lookup
218
+ const cached = await state.store.lookupPlural(lookupParams);
140
219
  if (category in cached) {
141
- return interpolateVars(cached[category], vars);
220
+ return new TranslationResult(interpolateVars(cached[category], vars), { pending: false, sourceLang: effectiveSourceLang, lang: targetLang });
221
+ }
222
+ // Tier 2: shared store lookup
223
+ if (state.sharedStore) {
224
+ const sharedCached = await state.sharedStore.lookupPlural(lookupParams);
225
+ if (category in sharedCached) {
226
+ // Propagate all forms to local store
227
+ for (const [cat, text] of Object.entries(sharedCached)) {
228
+ await state.store.insertPlural({
229
+ ...lookupParams, stringContext: context ?? '',
230
+ pluralCategory: cat, translatedText: text,
231
+ model: cfg.backendModel, status: 'translated',
232
+ });
233
+ }
234
+ return new TranslationResult(interpolateVars(sharedCached[category], vars), { pending: false, sourceLang: effectiveSourceLang, lang: targetLang });
235
+ }
142
236
  }
143
237
  // Read-only mode: skip backend, fall back to source forms
144
238
  if (cfg.readOnly) {
145
- const fallbackCategory = getPluralCategory(cfg.sourceLang, count);
239
+ const fallbackCategory = getPluralCategory(effectiveSourceLang, count);
146
240
  const fallback = fallbackCategory === 'one' ? one : other;
147
- return interpolateVars(fallback, vars);
241
+ return new TranslationResult(interpolateVars(fallback, vars), { pending: false, sourceLang: effectiveSourceLang, lang: targetLang });
242
+ }
243
+ // Background mode
244
+ if (background) {
245
+ const bgKey = `plural|${sourceKey}|${effectiveSourceLang}|${targetLang}|${projectContextHash}|${stringContextHash}`;
246
+ if (!state.backgroundKeys.has(bgKey)) {
247
+ state.backgroundKeys.add(bgKey);
248
+ _doPluralTranslate(one, other, sourceKey, effectiveSourceLang, targetLang, projectContextHash, stringContextHash, context ?? '', cfg)
249
+ .finally(() => state.backgroundKeys.delete(bgKey));
250
+ }
251
+ const fallbackCategory = getPluralCategory(effectiveSourceLang, count);
252
+ const fallback = fallbackCategory === 'one' ? one : other;
253
+ return new TranslationResult(interpolateVars(fallback, vars), { pending: true, sourceLang: effectiveSourceLang, lang: targetLang });
148
254
  }
149
255
  // Cache miss — call backend
256
+ const translatedText = await _doPluralTranslate(one, other, sourceKey, effectiveSourceLang, targetLang, projectContextHash, stringContextHash, context ?? '', cfg);
257
+ if (translatedText !== null && category in translatedText) {
258
+ return new TranslationResult(interpolateVars(translatedText[category], vars), { pending: false, sourceLang: effectiveSourceLang, lang: targetLang });
259
+ }
260
+ // Fallback
261
+ const fallbackCategory = getPluralCategory(effectiveSourceLang, count);
262
+ const fallback = fallbackCategory === 'one' ? one : other;
263
+ return new TranslationResult(interpolateVars(fallback, vars), { pending: false, sourceLang: effectiveSourceLang, lang: targetLang });
264
+ }
265
+ async function _doPluralTranslate(one, other, sourceKey, sourceLang, targetLang, projectContextHash, stringContextHash, stringContext, cfg) {
150
266
  try {
151
- const forms = await backendTranslatePlural(one, other, cfg.sourceLang, state.targetLang, cfg.projectContext, context ?? null, cfg);
267
+ const forms = await backendTranslatePlural(one, other, sourceLang, targetLang, cfg.projectContext, stringContext || null, cfg);
152
268
  // Validate and store each form
153
269
  const validCategories = new Set(['zero', 'one', 'two', 'few', 'many', 'other']);
154
270
  const sourcePlaceholders = new Set([
@@ -157,14 +273,15 @@ export async function aitPlural(one, other, count, opts) {
157
273
  ]);
158
274
  if (typeof forms !== 'object' || forms === null || !('other' in forms)) {
159
275
  console.warn(`[transduck] Invalid plural response for: ${one} / ${other}`);
160
- const fallbackCategory = getPluralCategory(cfg.sourceLang, count);
161
- const fallback = fallbackCategory === 'one' ? one : other;
162
- return interpolateVars(fallback, vars);
276
+ return null;
163
277
  }
278
+ const lookupParams = {
279
+ sourceText: sourceKey, sourceLang, targetLang,
280
+ projectContextHash, stringContextHash,
281
+ };
164
282
  for (const [cat, text] of Object.entries(forms)) {
165
283
  if (!validCategories.has(cat) || !text)
166
284
  continue;
167
- // Validate placeholders
168
285
  const translatedPlaceholders = extractPlaceholders(text);
169
286
  let allPresent = true;
170
287
  for (const p of sourcePlaceholders) {
@@ -174,37 +291,19 @@ export async function aitPlural(one, other, count, opts) {
174
291
  }
175
292
  }
176
293
  const status = allPresent ? 'translated' : 'failed';
177
- await state.store.insertPlural({
178
- sourceText: sourceKey, sourceLang: cfg.sourceLang, targetLang: state.targetLang,
179
- projectContextHash, stringContextHash, stringContext: context ?? '',
294
+ const insertParams = {
295
+ ...lookupParams, stringContext,
180
296
  pluralCategory: cat, translatedText: text,
181
297
  model: cfg.backendModel, status,
182
- });
298
+ };
299
+ await state.store.insertPlural(insertParams);
300
+ if (state.sharedStore)
301
+ await state.sharedStore.insertPlural(insertParams);
183
302
  }
184
- // Select the right form
185
- if (category in forms) {
186
- const text = forms[category];
187
- const tp = extractPlaceholders(text);
188
- let allPresent = true;
189
- for (const p of sourcePlaceholders) {
190
- if (!tp.has(p)) {
191
- allPresent = false;
192
- break;
193
- }
194
- }
195
- if (allPresent) {
196
- return interpolateVars(text, vars);
197
- }
198
- }
199
- // Fallback
200
- const fallbackCategory = getPluralCategory(cfg.sourceLang, count);
201
- const fallback = fallbackCategory === 'one' ? one : other;
202
- return interpolateVars(fallback, vars);
303
+ return forms;
203
304
  }
204
305
  catch (err) {
205
306
  console.warn(`[transduck] Backend failed for plural: ${one} / ${other}`, err);
206
- const fallbackCategory = getPluralCategory(cfg.sourceLang, count);
207
- const fallback = fallbackCategory === 'one' ? one : other;
208
- return interpolateVars(fallback, vars);
307
+ return null;
209
308
  }
210
309
  }
@@ -4,4 +4,5 @@
4
4
  */
5
5
  import type { TransduckConfig } from '../config.js';
6
6
  export declare function translate(sourceText: string, sourceLang: string, targetLang: string, projectContext: string, stringContext: string | null, config: TransduckConfig): Promise<string>;
7
+ export declare function detectLanguage(text: string, config: TransduckConfig): Promise<string>;
7
8
  export declare function translatePlural(one: string, other: string, sourceLang: string, targetLang: string, projectContext: string, stringContext: string | null, config: TransduckConfig): Promise<Record<string, string>>;
@@ -33,6 +33,17 @@ export async function translate(sourceText, sourceLang, targetLang, projectConte
33
33
  });
34
34
  return response.content[0].text.trim();
35
35
  }
36
+ export async function detectLanguage(text, config) {
37
+ const client = await getClient(config);
38
+ const response = await client.messages.create({
39
+ model: config.backendModel,
40
+ max_tokens: 16,
41
+ temperature: 0.0,
42
+ system: 'What language is this text written in? Return only the uppercase ISO 639-1 code.',
43
+ messages: [{ role: 'user', content: text }],
44
+ });
45
+ return response.content[0].text.trim().toUpperCase();
46
+ }
36
47
  export async function translatePlural(one, other, sourceLang, targetLang, projectContext, stringContext, config) {
37
48
  const client = await getClient(config);
38
49
  const messages = buildPluralMessages({
@@ -5,4 +5,5 @@
5
5
  */
6
6
  import type { TransduckConfig } from '../config.js';
7
7
  export declare function translate(sourceText: string, sourceLang: string, targetLang: string, projectContext: string, stringContext: string | null, config: TransduckConfig): Promise<string>;
8
+ export declare function detectLanguage(text: string, config: TransduckConfig): Promise<string>;
8
9
  export declare function translatePlural(one: string, other: string, sourceLang: string, targetLang: string, projectContext: string, stringContext: string | null, config: TransduckConfig): Promise<Record<string, string>>;
@@ -37,6 +37,12 @@ export async function translate(sourceText, sourceLang, targetLang, projectConte
37
37
  });
38
38
  return translateWithSdk(prompt);
39
39
  }
40
+ export async function detectLanguage(text, config) {
41
+ ensureToken(config);
42
+ const prompt = 'What language is this text written in? Return only the uppercase ISO 639-1 code.\n\n' + text;
43
+ const raw = await translateWithSdk(prompt);
44
+ return raw.trim().toUpperCase();
45
+ }
40
46
  export async function translatePlural(one, other, sourceLang, targetLang, projectContext, stringContext, config) {
41
47
  ensureToken(config);
42
48
  const prompt = buildPluralSinglePrompt({
@@ -5,6 +5,7 @@ import type { TransduckConfig } from '../config.js';
5
5
  export interface TranslationProvider {
6
6
  translate(sourceText: string, sourceLang: string, targetLang: string, projectContext: string, stringContext: string | null, config: TransduckConfig, _clientOverride?: any): Promise<string>;
7
7
  translatePlural(one: string, other: string, sourceLang: string, targetLang: string, projectContext: string, stringContext: string | null, config: TransduckConfig, _clientOverride?: any): Promise<Record<string, string>>;
8
+ detectLanguage(text: string, config: TransduckConfig, _clientOverride?: any): Promise<string>;
8
9
  }
9
10
  /**
10
11
  * Return the provider module for the configured provider.
@@ -3,4 +3,5 @@
3
3
  */
4
4
  import type { TransduckConfig } from '../config.js';
5
5
  export declare function translate(sourceText: string, sourceLang: string, targetLang: string, projectContext: string, stringContext: string | null, config: TransduckConfig, _clientOverride?: any): Promise<string>;
6
+ export declare function detectLanguage(text: string, config: TransduckConfig, _clientOverride?: any): Promise<string>;
6
7
  export declare function translatePlural(one: string, other: string, sourceLang: string, targetLang: string, projectContext: string, stringContext: string | null, config: TransduckConfig, _clientOverride?: any): Promise<Record<string, string>>;
@@ -20,6 +20,23 @@ export async function translate(sourceText, sourceLang, targetLang, projectConte
20
20
  });
21
21
  return response.choices[0].message.content.trim();
22
22
  }
23
+ export async function detectLanguage(text, config, _clientOverride) {
24
+ const apiKey = process.env[config.apiKeyEnv];
25
+ const client = _clientOverride ?? new OpenAI({
26
+ apiKey,
27
+ timeout: config.backendTimeout * 1000,
28
+ maxRetries: config.backendMaxRetries,
29
+ });
30
+ const response = await client.chat.completions.create({
31
+ model: config.backendModel,
32
+ messages: [
33
+ { role: 'system', content: 'What language is this text written in? Return only the uppercase ISO 639-1 code.' },
34
+ { role: 'user', content: text },
35
+ ],
36
+ temperature: 0.0,
37
+ });
38
+ return response.choices[0].message.content.trim().toUpperCase();
39
+ }
23
40
  export async function translatePlural(one, other, sourceLang, targetLang, projectContext, stringContext, config, _clientOverride) {
24
41
  const apiKey = process.env[config.apiKeyEnv];
25
42
  const client = _clientOverride ?? new OpenAI({
@@ -0,0 +1,19 @@
1
+ /**
2
+ * A string wrapper that carries translation metadata.
3
+ *
4
+ * Works in template literals, concatenation, and equality checks via
5
+ * toString() and valueOf() overrides, plus Symbol.toPrimitive.
6
+ */
7
+ export declare class TranslationResult extends String {
8
+ readonly pending: boolean;
9
+ readonly sourceLang: string;
10
+ readonly lang: string;
11
+ constructor(text: string, opts: {
12
+ pending: boolean;
13
+ sourceLang: string;
14
+ lang: string;
15
+ });
16
+ toString(): string;
17
+ valueOf(): string;
18
+ [Symbol.toPrimitive](_hint: string): string;
19
+ }
package/dist/result.js ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * A string wrapper that carries translation metadata.
3
+ *
4
+ * Works in template literals, concatenation, and equality checks via
5
+ * toString() and valueOf() overrides, plus Symbol.toPrimitive.
6
+ */
7
+ export class TranslationResult extends String {
8
+ pending;
9
+ sourceLang;
10
+ lang;
11
+ constructor(text, opts) {
12
+ super(text);
13
+ this.pending = opts.pending;
14
+ this.sourceLang = opts.sourceLang;
15
+ this.lang = opts.lang;
16
+ }
17
+ toString() {
18
+ return super.toString();
19
+ }
20
+ valueOf() {
21
+ return super.valueOf();
22
+ }
23
+ [Symbol.toPrimitive](_hint) {
24
+ return super.valueOf();
25
+ }
26
+ }
@@ -0,0 +1,18 @@
1
+ import type { LookupParams, InsertParams, InsertPluralParams } from './storage.js';
2
+ export declare class SharedStore {
3
+ private pool;
4
+ private url;
5
+ constructor(url: string);
6
+ initialize(): Promise<void>;
7
+ private getPool;
8
+ lookup(params: LookupParams): Promise<string | null>;
9
+ insert(params: InsertParams): Promise<void>;
10
+ lookupPlural(params: LookupParams): Promise<Record<string, string>>;
11
+ insertPlural(params: InsertPluralParams): Promise<void>;
12
+ stats(): Promise<{
13
+ totalTranslations: number;
14
+ totalFailed: number;
15
+ byLanguage: Record<string, number>;
16
+ }>;
17
+ close(): Promise<void>;
18
+ }