diff --git a/README.md b/README.md index 7617906..8b526c4 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,33 @@ graph TB ## 📦 Quick Start -### cURL Example +### cURL Examples + +#### DeepL Translation (Recommended) + +```bash +curl -X POST https://dplx.xi-xu.me/deepl \ + -H "Content-Type: application/json" \ + -d '{ + "text": "Hello, world!", + "source_lang": "EN", + "target_lang": "ZH" + }' +``` + +#### Google Translate + +```bash +curl -X POST https://dplx.xi-xu.me/google \ + -H "Content-Type: application/json" \ + -d '{ + "text": "Hello, world!", + "source_lang": "EN", + "target_lang": "ZH" + }' +``` + +#### Legacy Endpoint (DeepL) ```bash curl -X POST https://dplx.xi-xu.me/translate \ @@ -144,11 +170,13 @@ curl -X POST https://dplx.xi-xu.me/translate \ }' ``` -### JavaScript Example +### JavaScript Examples + +#### Using DeepL Translation ```javascript -async function translate(text, sourceLang = 'auto', targetLang = 'zh') { - const response = await fetch('https://dplx.xi-xu.me/translate', { +async function translateWithDeepL(text, sourceLang = 'auto', targetLang = 'zh') { + const response = await fetch('https://dplx.xi-xu.me/deepl', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -165,19 +193,47 @@ async function translate(text, sourceLang = 'auto', targetLang = 'zh') { } // Usage example -translate('Hello, world!', 'en', 'zh') +translateWithDeepL('Hello, world!', 'en', 'zh') .then(result => console.log(result)) .catch(error => console.error(error)); ``` -### Python Example +#### Using Google Translate + +```javascript +async function translateWithGoogle(text, sourceLang = 'auto', targetLang = 'zh') { + const response = await fetch('https://dplx.xi-xu.me/google', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + text: text, + source_lang: sourceLang, + target_lang: targetLang + }) + }); + + const result = await response.json(); + return result.data; +} + +// Usage example +translateWithGoogle('Hello, world!', 'en', 'zh') + .then(result => console.log(result)) + .catch(error => console.error(error)); +``` + +### Python Examples + +#### Using DeepL Translation ```python import requests import json -def translate(text, source_lang='auto', target_lang='zh'): - url = 'https://dplx.xi-xu.me/translate' +def translate_with_deepl(text, source_lang='auto', target_lang='zh'): + url = 'https://dplx.xi-xu.me/deepl' data = { 'text': text, 'source_lang': source_lang, @@ -194,7 +250,37 @@ def translate(text, source_lang='auto', target_lang='zh'): # Usage example try: - result = translate('Hello, world!', 'en', 'zh') + result = translate_with_deepl('Hello, world!', 'en', 'zh') + print(result) +except Exception as e: + print(f"Error: {e}") +``` + +#### Using Google Translate + +```python +import requests +import json + +def translate_with_google(text, source_lang='auto', target_lang='zh'): + url = 'https://dplx.xi-xu.me/google' + data = { + 'text': text, + 'source_lang': source_lang, + 'target_lang': target_lang + } + + response = requests.post(url, json=data) + result = response.json() + + if result['code'] == 200: + return result['data'] + else: + raise Exception(f"Translation failed: {result.get('message', 'Unknown error')}") + +# Usage example +try: + result = translate_with_google('Hello, world!', 'en', 'zh') print(result) except Exception as e: print(f"Error: {e}") @@ -220,14 +306,14 @@ A modern, free web-based translation app powered by the DeepLX API. Features inc 1. [Download and install Pot for your platform](https://github.com/pot-app/pot-desktop/releases/latest) 2. Open Pot settings and navigate to Service Settings -3. Configure the DeepL service type as DeepLX and set the custom URL to `https://dplx.xi-xu.me/translate` +3. Configure the DeepL service type as DeepLX and set the custom URL to `https://dplx.xi-xu.me/deepl` ### [Zotero](https://www.zotero.org/) (Open-source reference management app) 1. [Download and install Zotero for your platform](https://www.zotero.org/download/) 2. Download and install the [Translate for Zotero](https://github.com/windingwind/zotero-pdf-translate) plugin 3. Open Zotero settings and navigate to the Services section under Translation -4. Configure the translation service as DeepLX (API) and set the endpoint to `https://dplx.xi-xu.me/translate` after clicking the config button +4. Configure the translation service as DeepLX (API) and set the endpoint to `https://dplx.xi-xu.me/deepl` after clicking the config button ### [PDFMathTranslate (pdf2zh)](https://github.com/Byaidu/PDFMathTranslate) (Open-source PDF document translation tool) @@ -237,14 +323,14 @@ Refer to [Advanced Options](https://github.com/Byaidu/PDFMathTranslate?tab=readm 1. [Install Immersive Translate](https://immersivetranslate.com/download/) 2. Go to developer settings and enable beta testing features -3. Go to translation services and add a custom translation service DeepLX, configure the API URL to `https://dplx.xi-xu.me/translate` +3. Go to translation services and add a custom translation service DeepLX, configure the API URL to `https://dplx.xi-xu.me/deepl` 4. Configure the maximum requests per second and maximum text length per request to appropriate values (e.g., `80` and `5000`) to ensure stability and performance ### [Bob](https://bobtranslate.com/) (Closed-source macOS app) 1. [Download and install Bob from the Mac App Store](https://apps.apple.com/app/id1630034110) 2. Download and install the [bob-plugin-deeplx](https://github.com/missuo/bob-plugin-deeplx) plugin -3. Configure the plugin to use `https://dplx.xi-xu.me/translate` +3. Configure the plugin to use `https://dplx.xi-xu.me/deepl` ## 🚀 Self-deployment @@ -329,7 +415,15 @@ For optimal performance and stability, it's recommended to deploy as many [XDPL] ## 📖 API Reference -### `/translate` +### Available Endpoints + +| Endpoint | Provider | Description | Status | +|----------|----------|-------------|---------| +| `/deepl` | DeepL | Primary DeepL translation endpoint | **Recommended** | +| `/google` | Google Translate | Google Translate endpoint | Active | +| `/translate` | DeepL | Legacy endpoint (uses DeepL) | Legacy | + +### `/deepl` (Recommended) **Request Method**: `POST` @@ -355,6 +449,60 @@ For optimal performance and stability, it's recommended to deploy as many [XDPL] } ``` +### `/google` + +**Request Method**: `POST` + +**Request Headers**: `Content-Type: application/json` + +**Request Parameters**: + +| Parameter | Type | Description | Required | +| - | - | - | - | +| `text` | string | Text to translate | Yes | +| `source_lang` | string | Source language code | No, default `AUTO` | +| `target_lang` | string | Target language code | No, default `EN` | + +**Response**: + +```json +{ + "code": 200, + "data": "Translation result", + "id": "Random identifier", + "source_lang": "Detected source language code", + "target_lang": "Target language code" +} +``` + +### `/translate` (Legacy) + +**Request Method**: `POST` + +**Request Headers**: `Content-Type: application/json` + +**Note**: This is a legacy endpoint that uses DeepL. For new integrations, please use `/deepl` instead. + +**Request Parameters**: + +| Parameter | Type | Description | Required | +| - | - | - | - | +| `text` | string | Text to translate | Yes | +| `source_lang` | string | Source language code | No, default `AUTO` | +| `target_lang` | string | Target language code | No, default `EN` | + +**Response**: + +```json +{ + "code": 200, + "data": "Translation result", + "id": "Random identifier", + "source_lang": "Detected source language code", + "target_lang": "Target language code" +} +``` + **Supported Language Codes**: - `AUTO` - Auto-detect (source language only) diff --git a/README.zh.md b/README.zh.md index 91b9c9c..6d8403e 100644 --- a/README.zh.md +++ b/README.zh.md @@ -134,6 +134,32 @@ graph TB ### cURL 示例 +#### DeepL 翻译(推荐) + +```bash +curl -X POST https://dplx.xi-xu.me/deepl \ + -H "Content-Type: application/json" \ + -d '{ + "text": "Hello, world!", + "source_lang": "EN", + "target_lang": "ZH" + }' +``` + +#### Google 翻译 + +```bash +curl -X POST https://dplx.xi-xu.me/google \ + -H "Content-Type: application/json" \ + -d '{ + "text": "Hello, world!", + "source_lang": "EN", + "target_lang": "ZH" + }' +``` + +#### 传统端点(DeepL) + ```bash curl -X POST https://dplx.xi-xu.me/translate \ -H "Content-Type: application/json" \ @@ -146,9 +172,11 @@ curl -X POST https://dplx.xi-xu.me/translate \ ### JavaScript 示例 +#### 使用 DeepL 翻译 + ```javascript -async function translate(text, sourceLang = 'auto', targetLang = 'zh') { - const response = await fetch('https://dplx.xi-xu.me/translate', { +async function translateWithDeepL(text, sourceLang = 'auto', targetLang = 'zh') { + const response = await fetch('https://dplx.xi-xu.me/deepl', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -165,19 +193,47 @@ async function translate(text, sourceLang = 'auto', targetLang = 'zh') { } // 使用示例 -translate('Hello, world!', 'en', 'zh') +translateWithDeepL('Hello, world!', 'en', 'zh') + .then(result => console.log(result)) + .catch(error => console.error(error)); +``` + +#### 使用 Google 翻译 + +```javascript +async function translateWithGoogle(text, sourceLang = 'auto', targetLang = 'zh') { + const response = await fetch('https://dplx.xi-xu.me/google', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + text: text, + source_lang: sourceLang, + target_lang: targetLang + }) + }); + + const result = await response.json(); + return result.data; +} + +// 使用示例 +translateWithGoogle('Hello, world!', 'en', 'zh') .then(result => console.log(result)) .catch(error => console.error(error)); ``` ### Python 示例 +#### 使用 DeepL 翻译 + ```python import requests import json -def translate(text, source_lang='auto', target_lang='zh'): - url = 'https://dplx.xi-xu.me/translate' +def translate_with_deepl(text, source_lang='auto', target_lang='zh'): + url = 'https://dplx.xi-xu.me/deepl' data = { 'text': text, 'source_lang': source_lang, @@ -194,7 +250,37 @@ def translate(text, source_lang='auto', target_lang='zh'): # 使用示例 try: - result = translate('Hello, world!', 'en', 'zh') + result = translate_with_deepl('Hello, world!', 'en', 'zh') + print(result) +except Exception as e: + print(f"错误: {e}") +``` + +#### 使用 Google 翻译 + +```python +import requests +import json + +def translate_with_google(text, source_lang='auto', target_lang='zh'): + url = 'https://dplx.xi-xu.me/google' + data = { + 'text': text, + 'source_lang': source_lang, + 'target_lang': target_lang + } + + response = requests.post(url, json=data) + result = response.json() + + if result['code'] == 200: + return result['data'] + else: + raise Exception(f"翻译失败: {result.get('message', '未知错误')}") + +# 使用示例 +try: + result = translate_with_google('Hello, world!', 'en', 'zh') print(result) except Exception as e: print(f"错误: {e}") @@ -220,14 +306,14 @@ except Exception as e: 1. [下载并安装适用于您平台的 Pot](https://github.com/pot-app/pot-desktop/releases/latest) 2. 打开 Pot 设置并导航到服务设置 -3. 将 DeepL 服务类型配置为 DeepLX,并将自定义 URL 配置为 `https://dplx.xi-xu.me/translate` +3. 将 DeepL 服务类型配置为 DeepLX,并将自定义 URL 配置为 `https://dplx.xi-xu.me/deepl` ### [Zotero](https://www.zotero.org/)(开源文献管理应用) 1. [下载并安装适用于您平台的 Zotero](https://www.zotero.org/download/) 2. 下载并安装 [Translate for Zotero](https://github.com/windingwind/zotero-pdf-translate) 插件 3. 打开 Zotero 设置并导航到翻译中的服务部分 -4. 将翻译服务配置为 DeepLX(API),并点击配置按钮后将接口配置为 `https://dplx.xi-xu.me/translate` +4. 将翻译服务配置为 DeepLX(API),并点击配置按钮后将接口配置为 `https://dplx.xi-xu.me/deepl` ### [PDFMathTranslate(pdf2zh)](https://github.com/Byaidu/PDFMathTranslate)(开源 PDF 文档翻译工具) @@ -237,14 +323,14 @@ except Exception as e: 1. [安装沉浸式翻译](https://immersivetranslate.com/zh-Hans/download/) 2. 进入开发者设置并开启 beta 测试特性 -3. 进入翻译服务添加自定义翻译服务 DeepLX,将 API URL 配置为 `https://dplx.xi-xu.me/translate` +3. 进入翻译服务添加自定义翻译服务 DeepLX,将 API URL 配置为 `https://dplx.xi-xu.me/deepl` 4. 将每秒最大请求数和每次请求最大文本长度配置为合适的值(例如 `80` 和 `5000`),以确保稳定性和性能 ### [Bob](https://bobtranslate.com/)(闭源 macOS 应用) 1. [从 Mac App Store 下载并安装 Bob](https://apps.apple.com/cn/app/id1630034110) 2. 下载并安装 [bob-plugin-deeplx](https://github.com/missuo/bob-plugin-deeplx) 插件 -3. 配置插件使用 `https://dplx.xi-xu.me/translate` +3. 配置插件使用 `https://dplx.xi-xu.me/deepl` ## 🚀 自部署 @@ -329,7 +415,15 @@ npx wrangler deploy ## 📖 API 参考 -### `/translate` +### 可用端点 + +| 端点 | 服务提供商 | 描述 | 状态 | +|----------|----------|-------------|---------| +| `/deepl` | DeepL | 主要 DeepL 翻译端点 | **推荐** | +| `/google` | Google 翻译 | Google 翻译端点 | 活跃 | +| `/translate` | DeepL | 传统端点(使用 DeepL) | 传统 | + +### `/deepl`(推荐) **请求方法**:`POST` @@ -355,6 +449,60 @@ npx wrangler deploy } ``` +### `/google` + +**请求方法**:`POST` + +**请求标头**:`Content-Type: application/json` + +**请求参数**: + +| 参数 | 类型 | 说明 | 是否必要 | +| - | - | - | - | +| `text` | string | 要翻译的文本 | 是 | +| `source_lang` | string | 源语言代码 | 否,默认值 `AUTO` | +| `target_lang` | string | 目标语言代码 | 否,默认值 `EN` | + +**响应**: + +```json +{ + "code": 200, + "data": "翻译结果", + "id": "随机标识符", + "source_lang": "检测到的源语言代码", + "target_lang": "目标语言代码" +} +``` + +### `/translate`(传统) + +**请求方法**:`POST` + +**请求标头**:`Content-Type: application/json` + +**注意**:这是一个使用 DeepL 的传统端点。对于新集成,请使用 `/deepl`。 + +**请求参数**: + +| 参数 | 类型 | 说明 | 是否必要 | +| - | - | - | - | +| `text` | string | 要翻译的文本 | 是 | +| `source_lang` | string | 源语言代码 | 否,默认值 `AUTO` | +| `target_lang` | string | 目标语言代码 | 否,默认值 `EN` | + +**响应**: + +```json +{ + "code": 200, + "data": "翻译结果", + "id": "随机标识符", + "source_lang": "检测到的源语言代码", + "target_lang": "目标语言代码" +} +``` + **支持的语言代码**: - `AUTO` - 自动检测(仅作为源语言) diff --git a/src/index.ts b/src/index.ts index fee255b..2c64bd4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,7 +20,6 @@ import { validateLanguageCode, } from "./lib/security"; import { translateWithGoogle } from "./lib/services/googleTranslate"; -import { translateWithMicrosoft } from "./lib/services/microsoftTranslator"; import { createStandardResponse } from "./lib/types"; /** @@ -67,15 +66,12 @@ export default worker; /** * Common translation handler function - * Processes translation requests for DeepL, Google Translate, and Microsoft Translator + * Processes translation requests for both DeepL and Google Translate * @param c - Hono context - * @param provider - Translation provider ('deepl', 'google', or 'microsoft') + * @param provider - Translation provider ('deepl' or 'google') * @returns Translation response */ -async function handleTranslation( - c: any, - provider: "deepl" | "google" | "microsoft" -) { +async function handleTranslation(c: any, provider: "deepl" | "google") { const env = c.env; const clientIP = getSecureClientIP(c.req.raw) || "unknown"; @@ -163,11 +159,6 @@ async function handleTranslation( env, clientIP, }); - } else if (provider === "microsoft") { - result = await translateWithMicrosoft(validatedParams, { - env, - clientIP, - }); } else { // Use DeepL as default result = await query(validatedParams, { @@ -215,7 +206,6 @@ app .get("/translate", (c) => c.text("Please use POST method :)")) .get("/deepl", (c) => c.text("Please use POST method :)")) .get("/google", (c) => c.text("Please use POST method :)")) - .get("/microsoft", (c) => c.text("Please use POST method :)")) /** * Debug endpoint for request format validation and troubleshooting @@ -341,14 +331,6 @@ app return handleTranslation(c, "google"); }) - /** - * Microsoft Translator endpoint - * POST /microsoft - Uses Microsoft Translator service - */ - .post("/microsoft", async (c) => { - return handleTranslation(c, "microsoft"); - }) - /** * Catch-all route for undefined paths * Redirects all other requests to the GitHub repository diff --git a/src/lib/services/microsoftTranslator.ts b/src/lib/services/microsoftTranslator.ts deleted file mode 100644 index a8f3876..0000000 --- a/src/lib/services/microsoftTranslator.ts +++ /dev/null @@ -1,196 +0,0 @@ -/** - * Microsoft Translator integration service - * Uses Microsoft Edge's translate authentication endpoint - */ - -import { createErrorResponse } from "../errorHandler"; -import { - Config, - createStandardResponse, - RequestParams, - ResponseParams, -} from "../types"; - -/** - * Language code mapping for Microsoft Translator - * Maps DeepLX language codes to Microsoft Translator language codes - */ -const LANGUAGE_MAP: Record = { - auto: "", - zh: "zh-Hans", - "zh-cn": "zh-Hans", - "zh-tw": "zh-Hant", - "zh-hant": "zh-Hant", - 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", - pt: "pt", - vi: "vi", - id: "id", - th: "th", - ms: "ms", - ar: "ar", - hi: "hi", - "mn-cy": "mn-Cyrl", - "mn-mo": "mn-Mong", - km: "km", - "nb-no": "nb", - nb: "nb", - fa: "fa", - sv: "sv", - pl: "pl", - nl: "nl", - uk: "uk", - he: "he", -}; - -/** - * Normalize language code for Microsoft Translator API - */ -function normalizeLanguageCode(lang: string): string { - const normalized = lang.toLowerCase(); - return LANGUAGE_MAP[normalized] || normalized; -} - -/** - * Get authentication token from Microsoft Edge translate auth endpoint - */ -async function getAuthToken(): Promise { - const tokenUrl = "https://edge.microsoft.com/translate/auth"; - - const response = await fetch(tokenUrl, { - method: "GET", - headers: { - "User-Agent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0", - }, - }); - - if (!response.ok) { - throw new Error( - `Failed to get auth token: ${response.status} ${response.statusText}` - ); - } - - const token = await response.text(); - if (!token || token.trim() === "") { - throw new Error("Empty auth token received"); - } - - return token.trim(); -} - -/** - * Translate text using Microsoft Translator API - * @param params - Translation parameters (text, source_lang, target_lang) - * @param config - Configuration options - * @returns Translation response in DeepLX format - */ -export async function translateWithMicrosoft( - params: RequestParams, - config?: Config & { env?: any; clientIP?: string } -): Promise { - try { - const { text, source_lang, target_lang } = params; - - // Get authentication token - const authToken = await getAuthToken(); - - // Normalize language codes - const fromLang = normalizeLanguageCode(source_lang); - const toLang = normalizeLanguageCode(target_lang); - - // Construct the API URL with query parameters - const apiUrl = new URL( - "https://api-edge.cognitive.microsofttranslator.com/translate" - ); - apiUrl.searchParams.append("from", fromLang); - apiUrl.searchParams.append("to", toLang); - apiUrl.searchParams.append("api-version", "3.0"); - apiUrl.searchParams.append("includeSentenceLength", "true"); - - // Prepare request body - const requestBody = [{ Text: text }]; - - // Make the translation request - const response = await fetch(apiUrl.toString(), { - method: "POST", - headers: { - accept: "*/*", - "accept-language": - "zh-TW,zh;q=0.9,ja;q=0.8,zh-CN;q=0.7,en-US;q=0.6,en;q=0.5", - authorization: `Bearer ${authToken}`, - "cache-control": "no-cache", - "content-type": "application/json", - pragma: "no-cache", - "sec-ch-ua": - '"Microsoft Edge";v="120", "Chromium";v="120", "Not-A.Brand";v="24"', - "sec-ch-ua-mobile": "?0", - "sec-ch-ua-platform": '"Windows"', - "sec-fetch-dest": "empty", - "sec-fetch-mode": "cors", - "sec-fetch-site": "cross-site", - Referer: "https://translator.microsoft.com/", - "Referrer-Policy": "strict-origin-when-cross-origin", - "User-Agent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0", - }, - body: JSON.stringify(requestBody), - }); - - if (!response.ok) { - throw new Error( - `Microsoft Translator API responded with status ${response.status}: ${response.statusText}` - ); - } - - const responseData = await response.json(); - - // Parse the response - if (!Array.isArray(responseData) || responseData.length === 0) { - throw new Error("Invalid response format from Microsoft Translator"); - } - - const firstResult = responseData[0]; - if (!firstResult.translations || !Array.isArray(firstResult.translations)) { - throw new Error("No translations found in response"); - } - - const translation = firstResult.translations[0]; - if (!translation || !translation.text) { - throw new Error("Empty translation result"); - } - - const translatedText = translation.text.trim(); - - // Detect source language if auto-detection was used - const detectedSourceLang = - firstResult.detectedLanguage?.language || fromLang; - - // Format the response to match the DeepLX API - return createStandardResponse( - 200, - translatedText, - Math.floor(Math.random() * 10000000000), - detectedSourceLang.toUpperCase(), - toLang.toUpperCase() - ); - } catch (error) { - console.error("Error in Microsoft Translator:", error); - - const errorResponse = createErrorResponse(error, { - endpoint: "/microsoft", - clientIP: config?.clientIP || "unknown", - }); - - return errorResponse.response; - } -} diff --git a/tests/integration/translation.test.ts b/tests/integration/translation.test.ts index 86d0222..c9e3a4d 100644 --- a/tests/integration/translation.test.ts +++ b/tests/integration/translation.test.ts @@ -45,77 +45,6 @@ describe("Translation Integration Tests", () => { expect(result.target_lang).toBe("ZH"); }); - it("should complete Microsoft Translator workflow", async () => { - // Mock Microsoft Translator auth token response - global.fetch = jest - .fn() - .mockResolvedValueOnce({ - ok: true, - text: () => Promise.resolve("mock-auth-token"), - }) - .mockResolvedValueOnce({ - ok: true, - json: () => - Promise.resolve([ - { - translations: [ - { - text: "你好世界", - to: "zh-Hans", - }, - ], - detectedLanguage: { - language: "en", - score: 1.0, - }, - }, - ]), - }); - - const { translateWithMicrosoft } = await import( - "../../src/lib/services/microsoftTranslator" - ); - - const result = await translateWithMicrosoft( - { - text: "Hello world", - source_lang: "en", - target_lang: "zh", - }, - { env: mockEnv, clientIP: "127.0.0.1" } - ); - - expect(result.code).toBe(200); - expect(result.data).toBe("你好世界"); - expect(result.source_lang).toBe("EN"); - expect(result.target_lang).toBe("ZH-HANS"); - - // Verify both auth and translate API calls were made - expect(global.fetch).toHaveBeenCalledTimes(2); - expect(global.fetch).toHaveBeenNthCalledWith( - 1, - "https://edge.microsoft.com/translate/auth", - expect.objectContaining({ - method: "GET", - headers: expect.objectContaining({ - "User-Agent": expect.stringContaining("Chrome"), - }), - }) - ); - expect(global.fetch).toHaveBeenNthCalledWith( - 2, - expect.stringContaining("api-edge.cognitive.microsofttranslator.com"), - expect.objectContaining({ - method: "POST", - headers: expect.objectContaining({ - authorization: "Bearer mock-auth-token", - "content-type": "application/json", - }), - body: JSON.stringify([{ Text: "Hello world" }]), - }) - ); - }); - it("should handle rate limiting in translation flow", async () => { // Mock rate limit exceeded (mockEnv.RATE_LIMIT_KV.get as jest.Mock).mockResolvedValue( diff --git a/tests/lib/services/microsoftTranslator.test.ts b/tests/lib/services/microsoftTranslator.test.ts deleted file mode 100644 index 968d31e..0000000 --- a/tests/lib/services/microsoftTranslator.test.ts +++ /dev/null @@ -1,239 +0,0 @@ -/** - * Unit tests for Microsoft Translator service - */ - -import { beforeEach, describe, expect, it } from "@jest/globals"; -import { translateWithMicrosoft } from "../../../src/lib/services/microsoftTranslator"; - -// Mock global fetch -const mockFetch = jest.fn(); -global.fetch = mockFetch; - -describe("Microsoft Translator Service", () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it("should successfully translate text", async () => { - // Mock successful auth token response - mockFetch - .mockResolvedValueOnce({ - ok: true, - text: () => Promise.resolve("mock-auth-token"), - }) - .mockResolvedValueOnce({ - ok: true, - json: () => - Promise.resolve([ - { - translations: [ - { - text: "你好世界", - to: "zh-Hans", - }, - ], - detectedLanguage: { - language: "en", - score: 1.0, - }, - }, - ]), - }); - - const result = await translateWithMicrosoft({ - text: "Hello world", - source_lang: "en", - target_lang: "zh", - }); - - expect(result.code).toBe(200); - expect(result.data).toBe("你好世界"); - expect(result.source_lang).toBe("EN"); - expect(result.target_lang).toBe("ZH-HANS"); - - // Verify correct API calls - expect(mockFetch).toHaveBeenCalledTimes(2); - expect(mockFetch).toHaveBeenNthCalledWith( - 1, - "https://edge.microsoft.com/translate/auth", - expect.objectContaining({ - method: "GET", - headers: expect.objectContaining({ - "User-Agent": expect.stringContaining("Chrome"), - }), - }) - ); - expect(mockFetch).toHaveBeenNthCalledWith( - 2, - expect.stringContaining("api-edge.cognitive.microsofttranslator.com"), - expect.objectContaining({ - method: "POST", - headers: expect.objectContaining({ - authorization: "Bearer mock-auth-token", - "content-type": "application/json", - }), - body: JSON.stringify([{ Text: "Hello world" }]), - }) - ); - }); - - it("should handle auto language detection", async () => { - // Mock successful auth token response - mockFetch - .mockResolvedValueOnce({ - ok: true, - text: () => Promise.resolve("mock-auth-token"), - }) - .mockResolvedValueOnce({ - ok: true, - json: () => - Promise.resolve([ - { - translations: [ - { - text: "Hello world", - to: "en", - }, - ], - detectedLanguage: { - language: "zh-Hans", - score: 0.95, - }, - }, - ]), - }); - - const result = await translateWithMicrosoft({ - text: "你好世界", - source_lang: "auto", - target_lang: "en", - }); - - expect(result.code).toBe(200); - expect(result.data).toBe("Hello world"); - expect(result.source_lang).toBe("ZH-HANS"); - expect(result.target_lang).toBe("EN"); - }); - - it("should handle auth token failure", async () => { - // Mock failed auth token response - mockFetch.mockResolvedValueOnce({ - ok: false, - status: 403, - statusText: "Forbidden", - }); - - const result = await translateWithMicrosoft({ - text: "Hello world", - source_lang: "en", - target_lang: "zh", - }); - - expect(result.code).toBeGreaterThanOrEqual(400); - expect(result.data).toBeNull(); - expect(mockFetch).toHaveBeenCalledTimes(1); - }); - - it("should handle translation API failure", async () => { - // Mock successful auth but failed translation - mockFetch - .mockResolvedValueOnce({ - ok: true, - text: () => Promise.resolve("mock-auth-token"), - }) - .mockResolvedValueOnce({ - ok: false, - status: 500, - statusText: "Internal Server Error", - }); - - const result = await translateWithMicrosoft({ - text: "Hello world", - source_lang: "en", - target_lang: "zh", - }); - - expect(result.code).toBeGreaterThanOrEqual(400); - expect(result.data).toBeNull(); - expect(mockFetch).toHaveBeenCalledTimes(2); - }); - - it("should handle empty auth token", async () => { - // Mock empty auth token response - mockFetch.mockResolvedValueOnce({ - ok: true, - text: () => Promise.resolve(""), - }); - - const result = await translateWithMicrosoft({ - text: "Hello world", - source_lang: "en", - target_lang: "zh", - }); - - expect(result.code).toBeGreaterThanOrEqual(400); - expect(result.data).toBeNull(); - expect(mockFetch).toHaveBeenCalledTimes(1); - }); - - it("should handle invalid translation response", async () => { - // Mock successful auth but invalid translation response - mockFetch - .mockResolvedValueOnce({ - ok: true, - text: () => Promise.resolve("mock-auth-token"), - }) - .mockResolvedValueOnce({ - ok: true, - json: () => Promise.resolve([{}]), // Empty translation object - }); - - const result = await translateWithMicrosoft({ - text: "Hello world", - source_lang: "en", - target_lang: "zh", - }); - - expect(result.code).toBeGreaterThanOrEqual(400); - expect(result.data).toBeNull(); - }); - - it("should normalize language codes correctly", async () => { - // Mock successful response - mockFetch - .mockResolvedValueOnce({ - ok: true, - text: () => Promise.resolve("mock-auth-token"), - }) - .mockResolvedValueOnce({ - ok: true, - json: () => - Promise.resolve([ - { - translations: [ - { - text: "你好世界", - to: "zh-Hans", - }, - ], - detectedLanguage: { - language: "en", - score: 1.0, - }, - }, - ]), - }); - - await translateWithMicrosoft({ - text: "Hello world", - source_lang: "EN", // Should be normalized to 'en' - target_lang: "ZH-CN", // Should be normalized to 'zh-Hans' - }); - - // Check that the URL contains correctly normalized language codes - const secondCall = mockFetch.mock.calls[1]; - const url = secondCall[0]; - expect(url).toContain("from=en"); - expect(url).toContain("to=zh-hans"); - }); -}); diff --git a/tests/utils/testHelpers.ts b/tests/utils/testHelpers.ts index 0ef5126..881dfbc 100644 --- a/tests/utils/testHelpers.ts +++ b/tests/utils/testHelpers.ts @@ -186,6 +186,12 @@ export function createTestEnvironment() { const env: Env = { CACHE_KV: cacheKV as any, RATE_LIMIT_KV: rateLimitKV as any, + + { status: 200 } + ) + ), + }), + } as any, ANALYTICS: { writeDataPoint: jest.fn(), } as any,