Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1042,7 +1042,8 @@
"deepl",
"libretranslate",
"baidu",
"openai"
"openai",
"languagewire"
]
},
"default": [
Expand Down Expand Up @@ -1105,6 +1106,16 @@
"default": null,
"description": "%config.openai_api_key%"
},
"i18n-ally.translate.languagewire.apiKey": {
"type": "string",
"default": null,
"description": "%config.languagewire_api_key%"
},
"i18n-ally.translate.languagewire.apiRoot": {
"type": "string",
"default": "https://lwt.languagewire.com/p/api/v1",
"description": "%config.languagewire_api_root%"
},
"i18n-ally.translate.openai.apiRoot": {
"type": "string",
"default": "https://api.openai.com",
Expand Down
10 changes: 9 additions & 1 deletion src/core/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export class Config {
return this.getConfig<SortCompare>('sortCompare') || 'binary'
}

static get sortLocale(): string | undefined{
static get sortLocale(): string | undefined {
return this.getConfig<string>('sortLocale')
}

Expand Down Expand Up @@ -572,6 +572,14 @@ export class Config {
return this.getConfig<string | null | undefined>('translate.libre.apiRoot')
}

static get languageWireApiKey() {
return this.getConfig<string | null | undefined>('translate.languagewire.apiKey')
}

static get languageWireApiRoot() {
return this.getConfig<string | null | undefined>('translate.languagewire.apiRoot')
}

static get openaiApiKey() {
return this.getConfig<string | null | undefined>('translate.openai.apiKey')
}
Expand Down
56 changes: 56 additions & 0 deletions src/translators/engines/languagewire.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import axios from 'axios'
import TranslateEngine, { TranslateOptions, TranslateResult } from './base'
import { Config } from '~/core'

export default class LanguageWireTranslate extends TranslateEngine {
apiRoot = 'https://lwt.languagewire.com/p/api/v1'

async translate(options: TranslateOptions) {
const apiKey = Config.languageWireApiKey
let apiRoot = this.apiRoot
if (Config.languageWireApiRoot) apiRoot = Config.languageWireApiRoot.replace(/\/$/, '')

Comment on lines +8 to +12
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Guard against missing API key and undefined target language.

Calling the API without a key or target language will fail with a 401/validation error and yields a poor UX. Throw early with a clear message.

Apply:

   async translate(options: TranslateOptions) {
     const apiKey = Config.languageWireApiKey
     let apiRoot = this.apiRoot
-    if (Config.languageWireApiRoot) apiRoot = Config.languageWireApiRoot.replace(/\/$/, '')
+    if (Config.languageWireApiRoot) apiRoot = Config.languageWireApiRoot.replace(/\/$/, '')
+
+    if (!apiKey) {
+      throw new Error('LanguageWire: missing API key. Configure i18n-ally.translate.languagewire.apiKey in settings.')
+    }
+    if (!options.to) {
+      throw new Error('LanguageWire: target language "to" is required.')
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async translate(options: TranslateOptions) {
const apiKey = Config.languageWireApiKey
let apiRoot = this.apiRoot
if (Config.languageWireApiRoot) apiRoot = Config.languageWireApiRoot.replace(/\/$/, '')
async translate(options: TranslateOptions) {
const apiKey = Config.languageWireApiKey
let apiRoot = this.apiRoot
if (Config.languageWireApiRoot) apiRoot = Config.languageWireApiRoot.replace(/\/$/, '')
if (!apiKey) {
throw new Error('LanguageWire: missing API key. Configure i18n-ally.translate.languagewire.apiKey in settings.')
}
if (!options.to) {
throw new Error('LanguageWire: target language "to" is required.')
}
// …rest of implementation…
}
🤖 Prompt for AI Agents
In src/translators/engines/languagewire.ts around lines 8 to 12, add early
guards to validate Config.languageWireApiKey and the requested target language
before making the API call: if the API key is missing/empty throw a clear Error
like "LanguageWire API key is missing: set Config.languageWireApiKey", and if
the TranslateOptions does not include a target language (e.g.,
options.targetLanguage or options.target) throw "Missing target language in
translate options"; do these checks immediately after reading apiKey and
options, so the function fails fast with descriptive messages rather than
letting the request go to the API and return a 401/validation error.

const response = await axios.post(
`${apiRoot}/translations/text`,
{
sourceText: options.text,
reference: {
source: 'USER',
},
targetLanguage: options.to,
// If source language is specified, include it
...(options.from !== 'auto' && { sourceLanguage: options.from }),
},
{
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
},
)

return this.transform(response, options)
}

transform(response: any, options: TranslateOptions): TranslateResult {
const { text, from = 'auto', to = 'auto' } = options

// Get the translated text from the response
const translatedText = response.data.translation?.trim()

// Determine the detected source language
const detectedSource = response.data.detectedSourceLanguage?.mmtCode || from

const r: TranslateResult = {
text,
to,
from: detectedSource,
response,
result: translatedText ? [translatedText] : undefined,
linkToResult: '',
}

return r
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Harden transform against non-string translations and missing fields.

If the API returns a non-string translation, translation?.trim() will throw. Also, keep result consistently typed.

   transform(response: any, options: TranslateOptions): TranslateResult {
     const { text, from = 'auto', to = 'auto' } = options
 
-    // Get the translated text from the response
-    const translatedText = response.data.translation?.trim()
+    // Get the translated text from the response
+    const raw = response?.data?.translation
+    const translatedText = typeof raw === 'string' ? raw.trim() : undefined
 
-    // Determine the detected source language
-    const detectedSource = response.data.detectedSourceLanguage?.mmtCode || from
+    // Determine the detected source language
+    const detectedSource = response?.data?.detectedSourceLanguage?.mmtCode || from
 
     const r: TranslateResult = {
       text,
       to,
       from: detectedSource,
       response,
       result: translatedText ? [translatedText] : undefined,
       linkToResult: '',
     }
 
     return r
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
transform(response: any, options: TranslateOptions): TranslateResult {
const { text, from = 'auto', to = 'auto' } = options
// Get the translated text from the response
const translatedText = response.data.translation?.trim()
// Determine the detected source language
const detectedSource = response.data.detectedSourceLanguage?.mmtCode || from
const r: TranslateResult = {
text,
to,
from: detectedSource,
response,
result: translatedText ? [translatedText] : undefined,
linkToResult: '',
}
return r
}
transform(response: any, options: TranslateOptions): TranslateResult {
const { text, from = 'auto', to = 'auto' } = options
// Get the translated text from the response
const raw = response?.data?.translation
const translatedText = typeof raw === 'string' ? raw.trim() : undefined
// Determine the detected source language
const detectedSource = response?.data?.detectedSourceLanguage?.mmtCode || from
const r: TranslateResult = {
text,
to,
from: detectedSource,
response,
result: translatedText ? [translatedText] : undefined,
linkToResult: '',
}
return r
}
🤖 Prompt for AI Agents
src/translators/engines/languagewire.ts around lines 36 to 55, the transform()
assumes response.data.translation is a string and calls .trim(), which will
throw for non-strings or when fields are missing; also response.data may be
undefined and result should remain a string[] | undefined. Fix by defensively
reading nested fields (guard response && response.data), check typeof
translation === 'string' before trimming, otherwise coerce to
String(response.data.translation) only if not null/undefined or skip; set
translatedText to the trimmed string or undefined; ensure result is either an
array of strings when a valid translation exists or undefined; similarly guard
detectedSourceLanguage access and default to options.from.

}
3 changes: 3 additions & 0 deletions src/translators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import DeepLTranslateEngine from './engines/deepl'
import LibreTranslateEngine from './engines/libretranslate'
import BaiduTranslate from './engines/baidu'
import OpenAITranslateEngine from './engines/openai'
import LanguageWireTranslate from './engines/languagewire'

export class Translator {
engines: Record<string, TranslateEngine> ={
Expand All @@ -14,6 +15,7 @@ export class Translator {
'libretranslate': new LibreTranslateEngine(),
'baidu': new BaiduTranslate(),
'openai': new OpenAITranslateEngine(),
'languagewire': new LanguageWireTranslate(),
}

async translate(options: TranslateOptions & { engine: string }) {
Expand All @@ -30,6 +32,7 @@ export {
LibreTranslateEngine,
BaiduTranslate,
OpenAITranslateEngine,
LanguageWireTranslate,
}

export * from './engines/base'