diff --git a/changelog/2025.mdx b/changelog/2025.mdx index fac0a78..9cf07ac 100644 --- a/changelog/2025.mdx +++ b/changelog/2025.mdx @@ -74,6 +74,26 @@ import { useState, useEffect } from 'react'; return ; })()} + +
+ +## Dec 07, 2025 +--- +**Evolving Incentives: maintain Lock-1ups for Long-term Ecosystem Management** + +
+ +
+ +An optional 11 aa lock-up feature for rewards from high-incentive tasks (e.g., in Airdrop or Frontier campaigns). + +- **Trigger:** Rewards are deposited to your balance after a campaign. +- **Lock:** Secure rewards by locking them in a smart contract via the Assets page. +- **Release:** After the lock-up period, claim rewards directly to your wallet with one click. + +This is a key step in evolving our incentive system toward granular management. It provides a tool to manage liquidity in high-reward scenarios, balancing short-term engagement with the ecosystem's long-term health. + +
## Dec 05, 2025 @@ -84,7 +104,7 @@ import { useState, useEffect } from 'react';
-An optional lock-up feature for rewards from high-incentive tasks (e.g., in Airdrop or Frontier campaigns). +An optional 11 aa lock-up feature for rewards from high-incentive tasks (e.g., in Airdrop or Frontier campaigns). - **Trigger:** Rewards are deposited to your balance after a campaign. - **Lock:** Secure rewards by locking them in a smart contract via the Assets page. @@ -93,11 +113,13 @@ An optional lock-up feature for rewards from high-incentive tasks (e.g., in Aird This is a key step in evolving our incentive system toward granular management. It provides a tool to manage liquidity in high-reward scenarios, balancing short-term engagement with the ecosystem's long-term health.
+ +
## Dec 04, 2025 --- -**Evolving Incentives: Reward Lock-ups for Long-term Ecosystem Management** +**Evolving Incentives: Reward 11 Lock-ups for Long-term Ecosystem Management**
diff --git a/node_modules/.bin/openai b/node_modules/.bin/openai index d3dcb0b..1d3f518 120000 --- a/node_modules/.bin/openai +++ b/node_modules/.bin/openai @@ -1 +1,16 @@ -../openai/bin/cli \ No newline at end of file +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/../openai/bin/cli" "$@" +else + exec node "$basedir/../openai/bin/cli" "$@" +fi diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index c37eee6..4472d4e 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -4,6 +4,149 @@ "lockfileVersion": 3, "requires": true, "packages": { + "node_modules/@octokit/auth-token": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", + "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.3", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/endpoint": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.2.tgz", + "integrity": "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/graphql": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", + "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-retry": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.0.3.tgz", + "integrity": "sha512-vKGx1i3MC0za53IzYBSBXcrhmd+daQDzuZfYDd52X5S0M2otf3kVZTVP8bLA3EkU0lTvd1WEC2OlNNa4G+dohA==", + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=7" + } + }, + "node_modules/@octokit/request": { + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.7.tgz", + "integrity": "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.2", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "fast-content-type-parse": "^3.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/request-error": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", + "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^27.0.0" + } + }, + "node_modules/before-after-hook": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "license": "Apache-2.0" + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "license": "MIT" + }, + "node_modules/fast-content-type-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", + "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/fs-extra": { "version": "11.3.3", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", @@ -57,6 +200,12 @@ } } }, + "node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "license": "ISC" + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", diff --git a/package-lock.json b/package-lock.json index a505679..46a72ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,155 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@octokit/core": "^7.0.6", + "@octokit/plugin-retry": "^8.0.3", "fs-extra": "^11.3.3", "openai": "^6.15.0" } }, + "node_modules/@octokit/auth-token": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", + "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.3", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/endpoint": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.2.tgz", + "integrity": "sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/graphql": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", + "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-retry": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.0.3.tgz", + "integrity": "sha512-vKGx1i3MC0za53IzYBSBXcrhmd+daQDzuZfYDd52X5S0M2otf3kVZTVP8bLA3EkU0lTvd1WEC2OlNNa4G+dohA==", + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=7" + } + }, + "node_modules/@octokit/request": { + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.7.tgz", + "integrity": "sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.2", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "fast-content-type-parse": "^3.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/request-error": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", + "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^27.0.0" + } + }, + "node_modules/before-after-hook": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "license": "Apache-2.0" + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "license": "MIT" + }, + "node_modules/fast-content-type-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", + "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/fs-extra": { "version": "11.3.3", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", @@ -66,6 +211,12 @@ } } }, + "node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "license": "ISC" + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", diff --git a/package.json b/package.json index 6d076af..106de78 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,10 @@ "keywords": [], "author": "", "license": "ISC", + "type": "module", "dependencies": { + "@octokit/core": "^7.0.6", + "@octokit/plugin-retry": "^8.0.3", "fs-extra": "^11.3.3", "openai": "^6.15.0" } diff --git a/translate.js b/translate.js index bcb4aea..b6b3bf1 100644 --- a/translate.js +++ b/translate.js @@ -3,97 +3,206 @@ import fs from "fs-extra"; import path from "path"; /** - * =============================== * 配置区 - * =============================== */ - -// 原始 changelog 目录(英文) const SRC_DIR = "changelog"; - -// 输出语言配置 const TARGET_LANGS = [ { code: "cn", name: "Chinese", systemPrompt: - "请将以下英文 changelog 翻译成简体中文,要求:1. 语言简洁、专业,适合开发人员和技术文档阅读。2. 只翻译纯文本部分,忽略任何 HTML 标签、代码块、表格、特殊格式(如代码行、列)等。3. 保留原有 HTML 标签和结构,不要修改格式。4. 保证翻译内容准确,语言简洁。", + "请将以下英文 changelog 翻译成简体中文,要求:1. 语言简洁、专业,适合开发人员和技术文档阅读。2. 只翻译纯文本部分,忽略任何 HTML 标签、代码块、表格、特殊格式(如代码行、列)等,看着像代码也保留不动。3. 保留原有 HTML 标签和结构,不要修改格式。4. 保证翻译内容准确,语言简洁。5.日期等保持原样,不要翻译。6.小标题的单词也要翻译", }, { code: "ko", name: "Korean", systemPrompt: - "Please translate the following English changelog into professional Korean, ensuring that: 1. The language is concise and suitable for technical documentation. 2. Only translate the text content, ignore code blocks, JavaScript code, tables, and special formatting (such as code lines, columns, components, etc.). 3. Preserve the original paragraph and heading (#) formats. 4. Do not translate or display any code or dynamic content.", + "다음 영어 changelog 를 전문적인 한국어로 번역해 주세요. 다음 요구사항을 엄격히 준수하세요: 1. 언어는 간결하고 전문적이며, 개발자와 기술 문서 읽는 사람에게 적합해야 합니다. 2. 텍스트 내용만 번역하고, HTML 태그, 코드 블록, 표, 특수 형식(예: 코드 행, 열 등) 등은 무시하고, 코드로 보이는 모든 내용은 그대로 유지하세요. 3. 원본 HTML 태그와 구조를 유지하고, 형식을 수정하지 마세요. 4. 번역 내용의 정확성을 보장하고, 언어는 간결하게 유지하세요. 5. 날짜 등은 원본 그대로 유지하고 번역하지 마세요. 6. 소제목의 단어도 반드시 번역하세요.", }, ]; - -// OpenAI 客户端 +// 初始化客户端 const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, + timeout: 120000, + maxRetries: 0, }); /** - * =============================== - * 翻译函数 - * =============================== + * 重试策略 + */ +async function withRetry(fn, maxRetries = 5) { + let retries = 0; + while (retries < maxRetries) { + try { + return await fn(); + } catch (err) { + retries++; + if (retries >= maxRetries) { + throw new Error(`重试${maxRetries}次后仍失败:${err.message}`); + } + const delay = 1000 * Math.pow(2, retries); + console.log(`请求失败,${delay}ms 后重试(第 ${retries}/${maxRetries} 次):`, err.message); + await new Promise(resolve => setTimeout(resolve, delay)); + } + } +} + +/** + * 分块函数(仅处理待翻译部分) + */ +function splitTextByParagraphs(text, maxChars = 8000) { + const paragraphs = text.split("\n\n"); + const chunks = []; + let currentChunk = ""; + + for (const para of paragraphs) { + if (para.length > maxChars) { + const subPara = para.split("\n"); + let subCurrent = ""; + for (const sub of subPara) { + if (subCurrent.length + sub.length <= maxChars) { + subCurrent += sub + "\n"; + } else { + chunks.push(subCurrent.trim()); + subCurrent = sub + "\n"; + } + } + if (subCurrent.trim()) chunks.push(subCurrent.trim()); + continue; + } + + if (currentChunk.length + para.length <= maxChars) { + currentChunk += para + "\n\n"; + } else { + chunks.push(currentChunk.trim()); + currentChunk = para + "\n\n"; + } + } + if (currentChunk.trim()) { + chunks.push(currentChunk.trim()); + } + console.log(`✅ 待翻译部分拆分为 ${chunks.length} 块,单块最大${maxChars}字符`); + return chunks; +} + +/** + * 核心:截断文本,保留目标注释行之前的内容 + */ +function truncateBeforeComment(text, commentMarker) { + const lines = text.split('\n'); + let splitIndex = -1; + + // 模糊匹配目标注释行(兼容缩进/空格) + for (let i = 0; i < lines.length; i++) { + if (lines[i].includes(commentMarker)) { + splitIndex = i; + break; + } + } + + if (splitIndex === -1) { + console.log("⚠️ 未找到目标注释行:'Component definitions - moved to end of file for cleaner code organization',将翻译全部内容"); + return { translatePart: text, keepPart: "" }; + } + + const translateLines = lines.slice(0, splitIndex); + const keepLines = lines.slice(splitIndex); + + const translatePart = translateLines.join('\n').trim(); + const keepPart = keepLines.join('\n'); + + console.log(`✅ 文本截断完成: + - 待翻译部分:${translatePart.length} 字符 + - 保留部分(不翻译):${keepPart.length} 字符`); + return { translatePart, keepPart }; +} + +/** + * 翻译函数(整合截断+分块+翻译+拼接) */ async function translate(text, systemPrompt) { - const res = await client.chat.completions.create({ - model: "gpt-4o-mini", - messages: [ - { role: "system", content: systemPrompt }, - { role: "user", content: text }, - ], - }); - - return res.choices[0].message.content.trim(); + console.log("\n📝 原始文本总长度:", text.length, "字符"); + + // 1. 截断文本(关键:只翻译目标行之前的内容) + const commentMarker = "Component definitions - moved to end of file for cleaner code organization"; + const { translatePart, keepPart } = truncateBeforeComment(text, commentMarker); + + // 2. 无待翻译内容:直接返回保留部分 + if (!translatePart) { + return keepPart; + } + + // 3. 分块翻译待翻译部分 + const chunks = splitTextByParagraphs(translatePart); + const translatedChunks = []; + + for (let i = 0; i < chunks.length; i++) { + console.log(`🔄 翻译第 ${i+1}/${chunks.length} 块(字符数:${chunks[i].length})`); + const res = await withRetry(async () => { + return await client.chat.completions.create({ + model: "gpt-4o-mini", + messages: [ + { role: "system", content: systemPrompt }, + { role: "user", content: `请翻译以下文本,严格遵循系统指令:\n${chunks[i]}` }, + ], + temperature: 0.0, + max_tokens: 4096, + stream: false, + }); + }); + + if (!res || !res.choices || res.choices.length === 0) { + throw new Error(`第${i+1}块翻译失败:API返回异常`); + } + translatedChunks.push(res.choices[0].message.content.trim()); + } + + // 4. 合并翻译结果 + 拼接保留部分(原样) + const translatedPart = translatedChunks.join("\n\n"); + const finalResult = translatedPart + (keepPart ? "\n" + keepPart : ""); + + return finalResult; } /** - * =============================== * 主流程 - * =============================== */ async function run() { - // 没有 changelog 目录就直接退出 if (!(await fs.pathExists(SRC_DIR))) { - console.log("No changelog directory found, skip translation."); + console.log("❌ 未找到 changelog 目录,跳过翻译"); return; } const files = await fs.readdir(SRC_DIR); - for (const file of files) { - // 只处理 md / mdx 文件 if (!file.endsWith(".md") && !file.endsWith(".mdx")) continue; const srcPath = path.join(SRC_DIR, file); const content = await fs.readFile(srcPath, "utf-8"); - console.log(`Translating ${srcPath} ...`); + console.log(`\n========== 开始处理 ${srcPath} ==========`); for (const lang of TARGET_LANGS) { const outDir = path.join(lang.code, "changelog"); const outPath = path.join(outDir, file); - - // 确保目录存在 await fs.ensureDir(outDir); - // 调用翻译 - const translated = await translate(content, lang.systemPrompt); - - // 写入翻译后的文件 - await fs.writeFile(outPath, translated, "utf-8"); - - console.log(`✓ ${file} → ${lang.code}/changelog/${file}`); + try { + const translated = await translate(content, lang.systemPrompt); + await fs.writeFile(outPath, translated, "utf-8"); + console.log(`✅ 成功:${file} → ${lang.code}/changelog/${file}`); + } catch (err) { + console.error(`❌ 失败:${file} → ${lang.code}`, err.stack); + continue; + } } } - console.log("Translation completed."); + console.log("\n🎉 所有文件处理完成!"); } -// 执行 +// 执行主流程 run().catch((err) => { - console.error("Translation failed:", err); + console.error("💥 全局执行失败:", err.stack); process.exit(1); -}); +}); \ No newline at end of file