Skip to content

Commit 2d130d2

Browse files
authored
feat(bingtrans): add Bing Translate dictionary (#2341)
1 parent bcc8c6e commit 2d130d2

9 files changed

Lines changed: 381 additions & 0 deletions

File tree

src/app-config/dicts.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { SupportedLangs } from '@/_helpers/lang-check'
33
import alibaba from '@/components/dictionaries/alibaba/config'
44
import baidu from '@/components/dictionaries/baidu/config'
55
import bing from '@/components/dictionaries/bing/config'
6+
import bingtrans from '@/components/dictionaries/bingtrans/config'
67
import ahdict from '@/components/dictionaries/ahdict/config'
78
import oaldict from '@/components/dictionaries/oaldict/config'
89
import caiyun from '@/components/dictionaries/caiyun/config'
@@ -40,6 +41,7 @@ export const defaultAllDicts = {
4041
alibaba: alibaba(),
4142
baidu: baidu(),
4243
bing: bing(),
44+
bingtrans: bingtrans(),
4345
ahdict: ahdict(),
4446
oaldict: oaldict(),
4547
caiyun: caiyun(),

src/background/server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
const mv3BackgroundPreferredDicts = new Set<DictID>([
3838
'alibaba',
3939
'baidu',
40+
'bingtrans',
4041
'caiyun',
4142
'google',
4243
'niutrans',
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { MachineTrans as default } from '@/components/MachineTrans/MachineTrans'
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { getMachineLocales } from '../locales'
2+
3+
export const locales = getMachineLocales({
4+
en: 'Bing Translate',
5+
'zh-CN': '必应翻译',
6+
'zh-TW': '必應翻譯'
7+
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@import '@/components/MachineTrans/MachineTrans.scss';
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {
2+
MachineDictItem,
3+
machineConfig
4+
} from '@/components/MachineTrans/engine'
5+
import { Language } from '@opentranslate/translator'
6+
import { Subunion } from '@/typings/helpers'
7+
8+
export type BingtransLanguage = Subunion<
9+
Language,
10+
| 'zh-CN'
11+
| 'zh-TW'
12+
| 'en'
13+
| 'ja'
14+
| 'ko'
15+
| 'fr'
16+
| 'de'
17+
| 'es'
18+
| 'ru'
19+
| 'nl'
20+
| 'pt'
21+
>
22+
23+
export type BingtransConfig = MachineDictItem<BingtransLanguage>
24+
25+
export default (): BingtransConfig =>
26+
machineConfig<BingtransConfig>(
27+
['zh-CN', 'zh-TW', 'en', 'ja', 'ko', 'fr', 'de', 'es', 'ru', 'nl', 'pt'],
28+
{},
29+
{},
30+
{}
31+
)
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import axios from 'axios'
2+
import memoizeOne from 'memoize-one'
3+
import { SearchFunction, GetSrcPageFunction } from '../helpers'
4+
import {
5+
MachineTranslatePayload,
6+
MachineTranslateResult,
7+
getMTArgs
8+
} from '@/components/MachineTrans/engine'
9+
import {
10+
createLanguageHelper,
11+
emptyMachineResult,
12+
normalizeMachineLanguage,
13+
successMachineResult
14+
} from '../machine-custom'
15+
import { BingtransLanguage } from './config'
16+
17+
// ---------------------------------------------------------------------------
18+
// Microsoft / Bing Translator via the keyless Edge auth endpoint.
19+
//
20+
// Flow:
21+
// 1. GET https://edge.microsoft.com/translate/auth → JWT token (~10 min)
22+
// 2. POST https://api-edge.cognitive.microsofttranslator.com/translate
23+
// ?api-version=3.0&to=<tl>[&from=<sl>]
24+
// Body: [{ "Text": "..." }] Header: Authorization: Bearer <token>
25+
//
26+
// The same endpoints power the Edge browser's built-in translator, so no API
27+
// key or account is required.
28+
// ---------------------------------------------------------------------------
29+
30+
export const BING_AUTH_ENDPOINT = 'https://edge.microsoft.com/translate/auth'
31+
32+
export const BING_TRANSLATE_ENDPOINT =
33+
'https://api-edge.cognitive.microsofttranslator.com/translate'
34+
35+
const SUPPORTED_LANGUAGES: ReadonlyArray<BingtransLanguage> = [
36+
'zh-CN',
37+
'zh-TW',
38+
'en',
39+
'ja',
40+
'ko',
41+
'fr',
42+
'de',
43+
'es',
44+
'ru',
45+
'nl',
46+
'pt'
47+
]
48+
49+
/**
50+
* The auth token is valid for ~10 minutes. Cache it a little shorter so a
51+
* request never races the expiry, and avoid one auth round-trip per lookup.
52+
*/
53+
const AUTH_TOKEN_TTL = 8 * 60 * 1000
54+
55+
export const getTranslator = memoizeOne(() =>
56+
createLanguageHelper<BingtransLanguage>(SUPPORTED_LANGUAGES)
57+
)
58+
59+
export const getSrcPage: GetSrcPageFunction = (text, config, profile) => {
60+
const tl = profile.dicts.all.bingtrans.options.tl
61+
const lang =
62+
tl === 'default'
63+
? config.langCode === 'zh-CN'
64+
? 'zh-Hans'
65+
: config.langCode === 'zh-TW'
66+
? 'zh-Hant'
67+
: 'en'
68+
: mapBingLanguage(tl)
69+
70+
return `https://www.bing.com/translator/?from=auto&to=${lang}&text=${encodeURIComponent(
71+
text
72+
)}`
73+
}
74+
75+
export type BingtransResult = MachineTranslateResult<'bingtrans'>
76+
77+
/** Map Saladict / @opentranslate language codes → Microsoft Translator codes. */
78+
export function mapBingLanguage(lang: string): string {
79+
switch (lang) {
80+
case 'zh-CN':
81+
return 'zh-Hans'
82+
case 'zh-TW':
83+
return 'zh-Hant'
84+
default:
85+
return lang
86+
}
87+
}
88+
89+
export function buildBingTranslateParams(input: {
90+
sourceLanguage: string
91+
targetLanguage: string
92+
}): string {
93+
const params = new URLSearchParams()
94+
params.set('api-version', '3.0')
95+
params.set('to', mapBingLanguage(input.targetLanguage))
96+
// Omitting `from` lets Microsoft auto-detect the source language.
97+
if (input.sourceLanguage && input.sourceLanguage !== 'auto') {
98+
params.set('from', mapBingLanguage(input.sourceLanguage))
99+
}
100+
return params.toString()
101+
}
102+
103+
export function parseBingTranslatedText(
104+
data: any
105+
): {
106+
translatedText: string
107+
detectedLanguage?: string
108+
} {
109+
const entry = Array.isArray(data) ? data[0] : undefined
110+
return {
111+
translatedText: entry?.translations?.[0]?.text || '',
112+
detectedLanguage: entry?.detectedLanguage?.language
113+
}
114+
}
115+
116+
let cachedToken: { token: string; expiry: number } | null = null
117+
118+
export function resetBingAuthToken(): void {
119+
cachedToken = null
120+
}
121+
122+
async function getAuthToken(): Promise<string> {
123+
if (cachedToken && Date.now() < cachedToken.expiry) {
124+
return cachedToken.token
125+
}
126+
const response = await axios.get<string>(BING_AUTH_ENDPOINT, {
127+
responseType: 'text'
128+
})
129+
const token = typeof response.data === 'string' ? response.data.trim() : ''
130+
cachedToken = { token, expiry: Date.now() + AUTH_TOKEN_TTL }
131+
return token
132+
}
133+
134+
export const search: SearchFunction<
135+
BingtransResult,
136+
MachineTranslatePayload<BingtransLanguage>
137+
> = async (rawText, config, profile, payload) => {
138+
const translator = getTranslator()
139+
const langcodes = translator.getSupportLanguages()
140+
const { sl, tl, text } = await getMTArgs(
141+
translator as any,
142+
rawText,
143+
profile.dicts.all.bingtrans,
144+
config,
145+
payload
146+
)
147+
const sourceLanguage = payload.sl || 'auto'
148+
149+
try {
150+
const token = await getAuthToken()
151+
const response = await axios.post(
152+
`${BING_TRANSLATE_ENDPOINT}?${buildBingTranslateParams({
153+
sourceLanguage,
154+
targetLanguage: tl
155+
})}`,
156+
[{ Text: text }],
157+
{
158+
headers: {
159+
Authorization: `Bearer ${token}`,
160+
'Content-Type': 'application/json'
161+
}
162+
}
163+
)
164+
const parsed = parseBingTranslatedText(response.data)
165+
if (!parsed.translatedText) {
166+
return emptyMachineResult('bingtrans', sl, tl, langcodes)
167+
}
168+
return successMachineResult({
169+
id: 'bingtrans',
170+
sl: normalizeMachineLanguage(parsed.detectedLanguage || sourceLanguage),
171+
tl,
172+
slInitial: profile.dicts.all.bingtrans.options.slInitial,
173+
sourceText: text,
174+
translatedText: parsed.translatedText,
175+
langcodes
176+
})
177+
} catch (e) {
178+
return emptyMachineResult('bingtrans', sl, tl, langcodes)
179+
}
180+
}
440 Bytes
Loading

0 commit comments

Comments
 (0)