Files
smesh/src/services/translation.service.ts
mleku 13b3b82443 refactor: rebrand from Jumble to Smesh
- Replace all Jumble branding with Smesh throughout codebase
- Add new Smesh logo images (light/dark themes)
- Update Logo component to use PNG images with theme support
- Update URLs to git.mleku.dev/mleku/smesh
- Rename JumbleTranslate to SmeshTranslate
- Update all i18n locale files with new branding
- Add system theme detection CSS to prevent flash on load
- Update PWA manifest, docker-compose, and config files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-26 09:43:18 +02:00

137 lines
3.5 KiB
TypeScript

import { SMESH_API_BASE_URL } from '@/constants'
import client from '@/services/client.service'
import { TTranslationAccount } from '@/types'
class TranslationService {
static instance: TranslationService
private apiKeyMap: Record<string, string | undefined> = {}
private currentPubkey: string | null = null
constructor() {
if (!TranslationService.instance) {
TranslationService.instance = this
}
return TranslationService.instance
}
async getAccount(): Promise<TTranslationAccount> {
if (!this.currentPubkey) {
throw new Error('Please login first')
}
const apiKey = this.apiKeyMap[this.currentPubkey]
const path = '/v1/translation/account'
const method = 'GET'
let auth: string | undefined
if (!apiKey) {
auth = await client.signHttpAuth(
new URL(path, SMESH_API_BASE_URL).toString(),
method,
'Auth to get Smesh translation service account'
)
}
const act = await this._fetch<TTranslationAccount>({
path,
method,
auth,
retryWhenUnauthorized: !auth
})
if (act.api_key && act.pubkey) {
this.apiKeyMap[act.pubkey] = act.api_key
}
return act
}
async regenerateApiKey(): Promise<string> {
try {
const data = await this._fetch({
path: '/v1/translation/regenerate-api-key',
method: 'POST'
})
if (data.api_key && this.currentPubkey) {
this.apiKeyMap[this.currentPubkey] = data.api_key
}
return data.api_key
} catch (error) {
const errMsg = error instanceof Error ? error.message : ''
throw new Error(errMsg || 'Failed to regenerate API key')
}
}
async translate(text: string, target: string): Promise<string> {
if (!text) {
return text
}
try {
const data = await this._fetch({
path: '/v1/translation/translate',
method: 'POST',
body: JSON.stringify({ q: text, target })
})
const translatedText = data.translatedText
if (!translatedText) {
throw new Error('Translation failed')
}
return translatedText
} catch (error) {
const errMsg = error instanceof Error ? error.message : ''
throw new Error(errMsg || 'Failed to translate')
}
}
changeCurrentPubkey(pubkey: string | null): void {
this.currentPubkey = pubkey
}
private async _fetch<T = any>({
path,
method,
body,
auth,
retryWhenUnauthorized = true
}: {
path: string
method: string
body?: string
auth?: string
retryWhenUnauthorized?: boolean
}): Promise<T> {
if (!this.currentPubkey) {
throw new Error('Please login first')
}
const apiKey = this.apiKeyMap[this.currentPubkey]
const hasApiKey = !!apiKey
let _auth: string
if (auth) {
_auth = auth
} else if (hasApiKey) {
_auth = `Bearer ${apiKey}`
} else {
const act = await this.getAccount()
_auth = `Bearer ${act.api_key}`
}
const url = new URL(path, SMESH_API_BASE_URL).toString()
const response = await fetch(url, {
method,
headers: { 'Content-Type': 'application/json', Authorization: _auth },
body
})
const data = await response.json()
if (!response.ok) {
if (data.code === '00403' && hasApiKey && retryWhenUnauthorized) {
this.apiKeyMap[this.currentPubkey] = undefined
return this._fetch({ path, method, body, retryWhenUnauthorized: false })
}
throw new Error(data.error)
}
return data
}
}
const instance = new TranslationService()
export default instance