Skip to content

Commit

Permalink
feat(providers): bing translation (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
LittleSound authored Jul 12, 2024
1 parent 0d89328 commit f2d1541
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 34 deletions.
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@
"default": "",
"description": "Fallback language when the default language is not available."
},
"interline-translate.translator": {
"type": "string",
"default": "bing",
"enum": ["google", "bing"],
"description": "Translation service provider"
},
"interline-translate.googleTranslateProxy": {
"type": "string",
"default": "",
Expand Down
7 changes: 5 additions & 2 deletions src/controller/translateSelected.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { useExtensionContext } from '~/dependence'
import { useTranslationMeta } from '~/model/translator'
import { translate } from '~/providers/tranlations/google'
import { displayOnGapLines } from '~/view'
import { config } from '~/config'
import { translators } from '~/providers/tranlations'

export function RegisterTranslateSelectedText(ctx: Context) {
const extCtx = useExtensionContext(ctx)
Expand All @@ -24,10 +26,11 @@ export function RegisterTranslateSelectedText(ctx: Context) {

const meta = useTranslationMeta()

const translator = translators[config.translator]
const res = await translate({
text: activeEditor.document.getText(range),
from: meta.from as string as any,
to: meta.to as string as any,
from: meta.from as keyof typeof translator.supportLanguage,
to: meta.to as keyof typeof translator.supportLanguage,
})

if (!res.ok) {
Expand Down
9 changes: 5 additions & 4 deletions src/model/translator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import type { TextDocument } from 'vscode'
import { extractPhrases } from './extract'
import { CommentScopes, StringScopes, findScopesRange, isComment, isKeyword, isString, parseDocumentToTokens } from '~/model/grammar'
import { persistTranslationCache, useTranslationCache } from '~/model/cache'
import { translate } from '~/providers/tranlations/google'
import { config } from '~/config'
import type { Context } from '~/context'
import { translators } from '~/providers/tranlations'

export function useTranslationMeta() {
// TODO: use config or automatically recognize from language
Expand Down Expand Up @@ -99,10 +99,11 @@ export async function translateDocument(ctx: Context, options: TranslateDocument
if (!phrasesFromDoc.length)
return

const translationResult = await translate({
const translator = translators[config.translator]
const translationResult = await translator.translate({
text: phrasesFromDoc.join('\n'),
from: from as string as any,
to: to as string as any,
from: from as keyof typeof translator.supportLanguage,
to: to as keyof typeof translator.supportLanguage,
})

if (!translationResult.ok) {
Expand Down
141 changes: 141 additions & 0 deletions src/providers/tranlations/bing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { FetchError, ofetch } from 'ofetch'
import type { TranslationParameters, TranslationProviderInfo, TranslationResult } from './types'
import { createUnsupportedLanguageError } from './utils'
import { config } from '~/config'

export const info: TranslationProviderInfo = {
name: 'bing',
label: 'Bing Translate',
// https://learn.microsoft.com/zh-CN/azure/cognitive-services/translator/language-support
supportLanguage: {
'auto': '',
'zh-CN': 'zh-Hans',
'zh-TW': 'zh-Hant',
'yue': 'yue',
'en': 'en',
'ja': 'ja',
'ko': 'ko',
'fr': 'fr',
'es': 'es',
'ru': 'ru',
'de': 'de',
'it': 'it',
'tr': 'tr',
'pt-PT': 'pt-pt',
'pt-BR': 'pt',
'vi': 'vi',
'id': 'id',
'th': 'th',
'ms': 'ms',
'ar': 'ar',
'hi': 'hi',
'mn-Cyrl': 'mn-Cyrl',
'mn-Mong': 'mn-Mong',
'km': 'km',
'nb-NO': 'nb',
'fa': 'fa',
'sv': 'sv',
'pl': 'pl',
'nl': 'nl',
'uk': 'uk',
},
needs: [],
translate,
}

export type SupportLanguage = keyof typeof info.supportLanguage

export interface BingTranslationParameters extends TranslationParameters {
from: SupportLanguage
to: SupportLanguage
}

const tokenUrl = 'https://edge.microsoft.com/translate/auth'
const translatorUrl = 'https://api-edge.cognitive.microsofttranslator.com/translate'

function msgPerfix(text: string) {
return `[Interline Translate] Bing / ${text}`
}

export async function translate(options: BingTranslationParameters): Promise<TranslationResult> {
const { text, from, to } = options
const { supportLanguage } = info

if (text === '') {
return {
ok: false,
message: 'Empty Text',
originalError: null,
}
}

if (!(from in supportLanguage))
return createUnsupportedLanguageError('from', from)
if (!(to in supportLanguage))
return createUnsupportedLanguageError('to', to)

try {
const tokenRes = await ofetch(`${config.corsProxy}${tokenUrl}`, {
method: 'GET',
}).then(res => ({
ok: true as const,
data: res,
})).catch(e => ({
ok: false as const,
message: msgPerfix('Get Token Failed'),
originalError: e,
}))

if (!tokenRes.ok)
return tokenRes

// https://cn.bing.com/translator/?ref=TThis&text=good&from=en&to=es
const res = await ofetch(
`${config.corsProxy}${translatorUrl}`,
{
method: 'POST',
headers: {
authorization: `Bearer ${tokenRes.data}`,
},
query: {
'from': supportLanguage[from],
'to': supportLanguage[to],
'api-version': '3.0',
'includeSentenceLength': 'true',
},
body: [{ Text: text }],
},
)

if (!res[0].translations) {
console.error('Bing Translate Error with 200 status:', res)
throw res
}

return {
ok: true,
text: res[0].translations[0].text.trim(),
}
}
catch (e) {
if (e instanceof FetchError) {
let message = msgPerfix('Http Request Error')
if (e.status)
message = `\nHttp Status: ${e.status}\n${JSON.stringify(e.data)}`
message += '\nCheck your network connection or choose another translation provider'

return {
ok: false,
message,
originalError: e,
}
}
else {
return {
ok: false,
message: msgPerfix(typeof e === 'object' ? (e as any)?.message : 'Unknown Error'),
originalError: e,
}
}
}
}
8 changes: 5 additions & 3 deletions src/providers/tranlations/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { config } from '~/config'

export const info: TranslationProviderInfo = {
name: 'google',
label: 'Google Translate',
// https://cloud.google.com/translate/docs/languages?hl=zh-cn
supportLanguage: {
'auto': 'auto',
Expand Down Expand Up @@ -34,6 +35,7 @@ export const info: TranslationProviderInfo = {
place_hold: 'default: translate.google.com',
},
],
translate,
}

export type SupportLanguage = keyof typeof info.supportLanguage
Expand Down Expand Up @@ -69,9 +71,9 @@ export async function translate(options: GoogleTranslationParameters): Promise<T
`${config.corsProxy}https://${domain}/translate_a/single?dt=at&dt=bd&dt=ex&dt=ld&dt=md&dt=qca&dt=rw&dt=rm&dt=ss&dt=t`,
{
method: 'GET',
headers: {
Origin: 'https://translate.google.com',
},
// headers: {
// Origin: 'https://translate.google.com',
// },
query: {
client: 'gtx',
sl: supportLanguage[from],
Expand Down
7 changes: 5 additions & 2 deletions src/providers/tranlations/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { info as googleInfo } from './google'
import { info as bingInfo } from './bing'

export const supportLanguage = {
google: googleInfo.supportLanguage,
export const translators = {
google: googleInfo,
bing: bingInfo,
}
export const translatorOptions = Object.values(translators)
2 changes: 2 additions & 0 deletions src/providers/tranlations/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export interface TranslationParameters {

export interface TranslationProviderInfo {
name: string
label: string
supportLanguage: Record<string, string | undefined>
needs: { config_key: string; place_hold: string }[]
translate: (options: TranslationParameters) => Promise<TranslationResult>
}
38 changes: 15 additions & 23 deletions src/view/quickInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { config, languageOptions } from '~/config'
import type { Context } from '~/context'
import { useStore } from '~/store'
import type { Fn } from '~/types'
import { supportLanguage } from '~/providers/tranlations'
import { translatorOptions, translators } from '~/providers/tranlations'
import type { ConfigKeyTypeMap } from '~/generated-meta'

export function showTranslatePopmenu(ctx: Context) {
const store = useStore(ctx)
Expand Down Expand Up @@ -43,7 +44,7 @@ export function showTranslatePopmenu(ctx: Context) {
},
{
label: '$(cloud) Service:',
description: 'Google Translate',
description: translators[config.translator]?.label || `Unsupported: ${config.translator}`,
callback: () => showSetTranslationService(ctx),
},
])
Expand All @@ -63,9 +64,10 @@ export function showSetLanguagePopmenu(ctx: Context, type: 'target' | 'source')
? config.defaultTargetLanguage
: 'en'

const translatorName = config.translator || 'google'
quickPick.items = languageOptions
.filter(item => type === 'target'
? supportLanguage.google[item.description!]
? translators[translatorName].supportLanguage[item.description!]
: item.description === 'en',
)
.map((item) => {
Expand Down Expand Up @@ -112,29 +114,19 @@ export function showSetTranslationService(ctx: Context) {
quickPick.title = 'Translation Service'
quickPick.matchOnDescription = true
quickPick.matchOnDetail = true
defineQuickPickItems(quickPick, [
{
key: 'google',
label: 'Google Translate',
description: 'Powered by Google Translate',
},
// TODO add more translation services
// {
// label: 'Baidu Translate',
// description: 'Powered by Baidu Translate',
// },
// {
// label: 'Youdao Translate',
// description: 'Powered by Youdao Translate',
// },
// {
// label: 'More...',
// description: 'Install more translate sources from Extensions Marketplace',
// },
])
defineQuickPickItems(quickPick, translatorOptions.map(({ name, label }) => ({
label: name === config.translator ? `$(check) ${label}` : `$(array) ${label}`,
description: name,
})))

quickPick.onDidChangeSelection((selection) => {
window.showInformationMessage(`Selected service: ${selection[0].label}`)
const translatorName = selection[0].description
if (!translatorName || !(translatorName in translators)) {
window.showErrorMessage('Invalid service')
return
}
config.translator = translatorName as ConfigKeyTypeMap['interline-translate.translator']
showTranslatePopmenu(ctx)
})

Expand Down

0 comments on commit f2d1541

Please sign in to comment.