From 5825756ee9c6fb9713d1274dc566cde3b9cb2265 Mon Sep 17 00:00:00 2001 From: Jack <740172898@qq.com> Date: Thu, 28 Aug 2025 15:32:48 +0800 Subject: [PATCH] feat: implement comprehensive Chinese translation support - Add complete Chinese translation for all documentation files - Implement auto-translate workflow with GitHub Actions - Add multilingual support with language switcher - Update Starlight configuration for zh locale - Configure workflow to listen dev branch for automatic translation --- .github/workflows/auto-translate.yml | 32 + auto-translate/.gitignore | 97 +++ auto-translate/README.md | 139 +++ auto-translate/action.yml | 47 + auto-translate/index.ts | 409 +++++++++ auto-translate/package.json | 24 + auto-translate/script/publish | 41 + auto-translate/script/release | 54 ++ auto-translate/tsconfig.json | 40 + packages/opencode/src/provider/transform.ts | 29 +- packages/web/astro.config.mjs | 6 + packages/web/src/components/Header.astro | 115 ++- packages/web/src/config/language.ts | 44 + packages/web/src/content/docs/docs/index.mdx | 2 +- packages/web/src/content/docs/docs/sdk.mdx | 2 +- packages/web/src/content/docs/docs/server.mdx | 2 +- .../web/src/content/docs/zh/docs/agents.mdx | 628 ++++++++++++++ packages/web/src/content/docs/zh/docs/cli.mdx | 238 +++++ .../web/src/content/docs/zh/docs/commands.mdx | 270 ++++++ .../web/src/content/docs/zh/docs/config.mdx | 362 ++++++++ .../src/content/docs/zh/docs/enterprise.mdx | 96 ++ .../src/content/docs/zh/docs/formatters.mdx | 108 +++ .../web/src/content/docs/zh/docs/github.mdx | 131 +++ .../web/src/content/docs/zh/docs/gitlab.mdx | 151 ++++ packages/web/src/content/docs/zh/docs/ide.mdx | 46 + .../web/src/content/docs/zh/docs/index.mdx | 294 +++++++ .../web/src/content/docs/zh/docs/keybinds.mdx | 75 ++ packages/web/src/content/docs/zh/docs/lsp.mdx | 101 +++ .../src/content/docs/zh/docs/mcp-servers.mdx | 127 +++ .../web/src/content/docs/zh/docs/models.mdx | 127 +++ .../web/src/content/docs/zh/docs/modes.mdx | 328 +++++++ .../src/content/docs/zh/docs/permissions.mdx | 115 +++ .../web/src/content/docs/zh/docs/plugins.mdx | 102 +++ .../src/content/docs/zh/docs/providers.mdx | 817 ++++++++++++++++++ .../web/src/content/docs/zh/docs/rules.mdx | 152 ++++ packages/web/src/content/docs/zh/docs/sdk.mdx | 314 +++++++ .../web/src/content/docs/zh/docs/server.mdx | 177 ++++ .../web/src/content/docs/zh/docs/share.mdx | 127 +++ .../web/src/content/docs/zh/docs/themes.mdx | 367 ++++++++ .../content/docs/zh/docs/troubleshooting.mdx | 155 ++++ packages/web/src/content/docs/zh/docs/tui.mdx | 314 +++++++ packages/web/src/content/docs/zh/index.mdx | 12 + 42 files changed, 6777 insertions(+), 40 deletions(-) create mode 100644 .github/workflows/auto-translate.yml create mode 100644 auto-translate/.gitignore create mode 100644 auto-translate/README.md create mode 100644 auto-translate/action.yml create mode 100644 auto-translate/index.ts create mode 100644 auto-translate/package.json create mode 100755 auto-translate/script/publish create mode 100755 auto-translate/script/release create mode 100644 auto-translate/tsconfig.json create mode 100644 packages/web/src/config/language.ts create mode 100644 packages/web/src/content/docs/zh/docs/agents.mdx create mode 100644 packages/web/src/content/docs/zh/docs/cli.mdx create mode 100644 packages/web/src/content/docs/zh/docs/commands.mdx create mode 100644 packages/web/src/content/docs/zh/docs/config.mdx create mode 100644 packages/web/src/content/docs/zh/docs/enterprise.mdx create mode 100644 packages/web/src/content/docs/zh/docs/formatters.mdx create mode 100644 packages/web/src/content/docs/zh/docs/github.mdx create mode 100644 packages/web/src/content/docs/zh/docs/gitlab.mdx create mode 100644 packages/web/src/content/docs/zh/docs/ide.mdx create mode 100644 packages/web/src/content/docs/zh/docs/index.mdx create mode 100644 packages/web/src/content/docs/zh/docs/keybinds.mdx create mode 100644 packages/web/src/content/docs/zh/docs/lsp.mdx create mode 100644 packages/web/src/content/docs/zh/docs/mcp-servers.mdx create mode 100644 packages/web/src/content/docs/zh/docs/models.mdx create mode 100644 packages/web/src/content/docs/zh/docs/modes.mdx create mode 100644 packages/web/src/content/docs/zh/docs/permissions.mdx create mode 100644 packages/web/src/content/docs/zh/docs/plugins.mdx create mode 100644 packages/web/src/content/docs/zh/docs/providers.mdx create mode 100644 packages/web/src/content/docs/zh/docs/rules.mdx create mode 100644 packages/web/src/content/docs/zh/docs/sdk.mdx create mode 100644 packages/web/src/content/docs/zh/docs/server.mdx create mode 100644 packages/web/src/content/docs/zh/docs/share.mdx create mode 100644 packages/web/src/content/docs/zh/docs/themes.mdx create mode 100644 packages/web/src/content/docs/zh/docs/troubleshooting.mdx create mode 100644 packages/web/src/content/docs/zh/docs/tui.mdx create mode 100644 packages/web/src/content/docs/zh/index.mdx diff --git a/.github/workflows/auto-translate.yml b/.github/workflows/auto-translate.yml new file mode 100644 index 000000000000..ff3bf51bb82f --- /dev/null +++ b/.github/workflows/auto-translate.yml @@ -0,0 +1,32 @@ +name: Auto Translate + +on: + push: + branches: [dev] + paths: + - 'packages/web/src/content/docs/docs/*.mdx' + - 'packages/web/src/content/docs/docs/*.md' + +jobs: + translate: + runs-on: ubuntu-latest + permissions: + contents: write # Need write permission for creating branches, commits, etc. + id-token: write # Generate JWT token for GitHub API calls + pull-requests: write # Need write permission for creating pull requests + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: dev + fetch-depth: 0 # Need full history to detect changes + + - name: Run translation + uses: ./auto-translate + with: + model: anthropic/claude-sonnet-4-20250514 + source_lang: 'en' + target_lang: 'zh' + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} diff --git a/auto-translate/.gitignore b/auto-translate/.gitignore new file mode 100644 index 000000000000..892cbcbb7739 --- /dev/null +++ b/auto-translate/.gitignore @@ -0,0 +1,97 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +bun.lock + +# Environment variables +.env +.env.local +.env.*.local + +# Logs +*.log +logs/ + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ +*.lcov + +# nyc test coverage +.nyc_output + +# Dependency directories +jspm_packages/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Temporary files +*.tmp +*.temp diff --git a/auto-translate/README.md b/auto-translate/README.md new file mode 100644 index 000000000000..0be25db65d1d --- /dev/null +++ b/auto-translate/README.md @@ -0,0 +1,139 @@ +# Auto Translate Action + +Automatically translate English documentation to Chinese using GitHub Actions. + +## Features + +- 🚀 **Auto-trigger**: Automatically triggers translation when English docs are updated +- 🤖 **AI-powered**: Uses Claude AI for high-quality translation +- 📝 **Smart detection**: Automatically detects document changes (added, modified, deleted) +- 🔄 **Branch management**: Creates independent translation branches without affecting main branch +- 📋 **PR creation**: Automatically creates translation PRs for human review +- 🌏 **Multi-language support**: Supports multiple language pairs (currently English → Chinese) + +## Usage + +### 1. Reference in workflow + +```yaml +name: Auto Translate + +on: + push: + branches: [dev] + paths: + - 'packages/web/src/content/docs/docs/**/*.mdx' + - 'packages/web/src/content/docs/docs/**/*.md' + +jobs: + translate: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + id-token: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: dev + fetch-depth: 0 + + - name: Run translation + uses: ./auto-translate + with: + model: anthropic/claude-sonnet-4-20250514 + source_lang: 'en' + target_lang: 'zh' + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} +``` + +### 2. Configure environment variables + +Add to repository Secrets: +- `ANTHROPIC_API_KEY`: Anthropic API key + +### 3. Configure permissions + +Ensure workflow has sufficient permissions: +- `contents: write`: Update Chinese documentation +- `pull-requests: write`: Create translation PRs +- `id-token: write`: Generate JWT token + +## Workflow + +1. **Trigger detection**: Monitors English doc changes pushed to dev branch +2. **Change analysis**: Analyzes added, modified, and deleted documents +3. **AI translation**: Calls Claude AI for document translation +4. **Branch creation**: Creates independent translation branch +5. **File updates**: Updates corresponding Chinese documentation +6. **Commit & push**: Commits translation results and pushes to remote +7. **PR creation**: Automatically creates translation PR for review + +## Input Parameters + +| Parameter | Description | Required | Default | +|-----------|-------------|----------|---------| +| `model` | AI model | Yes | `anthropic/claude-sonnet-4-20250514` | +| `source_lang` | Source language | Yes | `en` | +| `target_lang` | Target language | Yes | `zh` | +| `token` | GitHub token | No | `GITHUB_TOKEN` | + +## Output + +- Automatically creates translation branch +- Updates Chinese documentation files +- Creates translation PR +- Detailed execution logs + +## Technical Architecture + +``` +Workflow → Action → Business Logic +├── Trigger condition check +├── Environment configuration +├── Dependency installation +├── Document change detection +├── AI translation call +├── Git operations +└── PR creation +``` + +## Requirements + +- Node.js 18+ +- Bun 1.0+ +- GitHub Actions v4 + +## Notes + +- Translation quality requires human review +- Ensure API key security configuration +- Recommend testing in test environment before deploying to production + +## Troubleshooting + +### Common Issues + +1. **Insufficient permissions**: Check workflow permission configuration +2. **API call failure**: Verify API key is correct +3. **Git operation failure**: Confirm repository access permissions +4. **Poor translation quality**: Adjust translation prompts or use better models + +### Log Viewing + +View detailed execution logs in GitHub Actions page, including: +- Document change detection results +- AI translation call status +- Git operation execution +- Error messages and stack traces + +## Contributing + +Welcome to submit Issues and Pull Requests to improve this Action! + +## License + +MIT License diff --git a/auto-translate/action.yml b/auto-translate/action.yml new file mode 100644 index 000000000000..7f1d2ea31b36 --- /dev/null +++ b/auto-translate/action.yml @@ -0,0 +1,47 @@ +name: "Auto Translate Action" +description: "Automatically translate English documentation to Chinese using AI" +branding: + icon: "globe" + color: "blue" + +inputs: + model: + description: "The AI model to use for translation. Takes the format of `provider/model`." + required: true + default: "anthropic/claude-sonnet-4-20250514" + + source_lang: + description: "Source language for translation." + required: true + default: "en" + + target_lang: + description: "Target language for translation." + required: true + default: "zh" + +runs: + using: "composite" + steps: + - name: Install opencode + shell: bash + run: curl -fsSL https://opencode.ai/install | bash + + - name: Install bun + shell: bash + run: npm install -g bun + + - name: Install dependencies + shell: bash + run: | + cd ${GITHUB_ACTION_PATH} + bun install + + - name: Run translation + shell: bash + run: bun ${GITHUB_ACTION_PATH}/index.ts + env: + MODEL: ${{ inputs.model }} + SOURCE_LANG: ${{ inputs.source_lang }} + TARGET_LANG: ${{ inputs.target_lang }} + TOKEN: ${{ github.token }} diff --git a/auto-translate/index.ts b/auto-translate/index.ts new file mode 100644 index 000000000000..3e44775a6f0f --- /dev/null +++ b/auto-translate/index.ts @@ -0,0 +1,409 @@ +#!/usr/bin/env bun + +import { $ } from "bun" +import path from "node:path" +import { Octokit } from "@octokit/rest" +import * as core from "@actions/core" +import * as github from "@actions/github" +import { createOpencodeClient } from "@opencode-ai/sdk" +import { spawn } from "node:child_process" +import fs from "node:fs" + +// Environment variables +const MODEL = process.env.MODEL || "anthropic/claude-sonnet-4-20250514" +const SOURCE_LANG = process.env.SOURCE_LANG || "en" +const TARGET_LANG = process.env.TARGET_LANG || "zh" + +// Path configuration +const DOCS_PATH = "packages/web/src/content/docs" +const SOURCE_DOCS_PATH = path.join(DOCS_PATH, "docs") +const TARGET_DOCS_PATH = path.join(DOCS_PATH, TARGET_LANG, "docs") + +// GitHub API client +let octokit: Octokit + +// Opencode client and server (exactly like opencode implementation) +const { client, server } = createOpencode() +let session: { id: string; title: string; version: string } +let gitConfig: string +let exitCode = 0 + +// Document change interface +interface DocChange { + path: string + changeType: "added" | "modified" | "deleted" + sourcePath: string + targetPath: string +} + +try { + await assertOpencodeConnected() + + // Initialize GitHub API client + octokit = new Octokit({ auth: process.env.TOKEN }) + + // Configure git + await configureGit(process.env.TOKEN!) + + // Setup opencode session + session = await client.session.create().then((r: any) => r.data) + console.log("opencode session", session.id) + + // Detect document changes + const changes = await detectDocChanges() + if (changes.length === 0) { + console.log("No documentation changes detected, skipping translation") + process.exit(0) + } + + // Create translation branch + const branchName = await createTranslationBranch() + + // Process each document change + console.log(`📝 Processing ${changes.length} document changes...`) + + for (let i = 0; i < changes.length; i++) { + const change = changes[i] + console.log(`\n🔄 Processing ${change.changeType}: ${change.path}`) + console.log(` Source: ${change.sourcePath}`) + console.log(` Target: ${change.targetPath}`) + + try { + await processDocChange(change) + console.log(`✅ Successfully processed: ${change.path}`) + } catch (error) { + console.error(`❌ Failed to process ${change.path}:`, error) + // Continue with next file instead of failing completely + } + } + + // Check if branch is dirty and commit changes + if (await branchIsDirty()) { + const commitMessage = "Documentation translation completed" + const prTitle = "Auto-translate: Documentation translation completed" + await commitAndPushTranslations(branchName, commitMessage) + await createTranslationPR(branchName, commitMessage, prTitle) + } + +} catch (e: any) { + exitCode = 1 + console.error(e) + let msg = e + if (e instanceof $.ShellError) { + msg = e.stderr.toString() + } else if (e instanceof Error) { + msg = e.message + } + core.setFailed(msg) +} finally { + server.close() + await restoreGitConfig() +} +process.exit(exitCode) + +function createOpencode() { + const host = "127.0.0.1" + const port = 4096 + const url = `http://${host}:${port}` + const proc = spawn(`opencode`, [`serve`, `--hostname=${host}`, `--port=${port}`]) + const client = createOpencodeClient({ baseUrl: url }) + + return { + server: { url, close: () => proc.kill() }, + client, + } +} + +async function assertOpencodeConnected() { + let retry = 0 + let connected = false + do { + try { + await client.app.get() + connected = true + break + } catch (e) {} + await new Promise((resolve) => setTimeout(resolve, 300)) + } while (retry++ < 30) + + if (!connected) { + throw new Error("Failed to connect to opencode server") + } +} + +async function configureGit(token: string) { + console.log("Configuring git...") + const config = "http.https://github.com/.extraheader" + const ret = await $`git config --local --get ${config}` + gitConfig = ret.stdout.toString().trim() + + const newCredentials = Buffer.from(`x-access-token:${token}`, "utf8").toString("base64") + + await $`git config --local --unset-all ${config}` + await $`git config --local ${config} "AUTHORIZATION: basic ${newCredentials}"` + await $`git config --global user.name "opencode-agent[bot]"` + await $`git config --global user.email "opencode-agent[bot]@users.noreply.github.com"` +} + +async function restoreGitConfig() { + if (gitConfig === undefined) return + console.log("Restoring git config...") + const config = "http.https://github.com/.extraheader" + await $`git config --local ${config} "${gitConfig}"` +} + +async function branchIsDirty() { + console.log("Checking if branch is dirty...") + const ret = await $`git status --porcelain` + return ret.stdout.toString().trim().length > 0 +} + +async function chat(text: string, files: any[] = []) { + console.log("Sending message to opencode...") + const [providerID, ...rest] = MODEL.split("/") + const modelID = rest.join("/") + + const chat = await client.session.chat({ + path: session, + body: { + providerID, + modelID, + agent: "build", + parts: [ + { + type: "text", + text, + }, + ...files.flatMap((f) => [ + { + type: "file" as const, + mime: f.mime, + url: `data:${f.mime};base64,${f.content}`, + filename: f.filename, + source: { + type: "file" as const, + text: { + value: f.replacement, + start: f.start, + end: f.end, + }, + path: f.filename, + }, + }, + ]), + ], + }, + }) + + // @ts-ignore + const match = chat.data.parts.findLast((p) => p.type === "text") + if (!match) throw new Error("Failed to parse the text response") + + return match.text +} + + + +// Custom functions for document translation +async function detectDocChanges(): Promise { + console.log("Detecting document changes...") + + const currentBranch = github.context.ref.replace('refs/heads/', '') + console.log(`Current branch: ${currentBranch}`) + + try { + const commits = await octokit.rest.repos.listCommits({ + owner: github.context.repo.owner, + repo: github.context.repo.repo, + sha: currentBranch, + per_page: 10 + }) + + if (commits.data.length < 2) { + console.log("No previous commits to compare") + return [] + } + + const latestCommit = commits.data[0] + const previousCommit = commits.data[1] + + console.log(`Latest commit: ${latestCommit.sha}`) + console.log(`Previous commit: ${previousCommit.sha}`) + + const diff = await octokit.rest.repos.compareCommits({ + owner: github.context.repo.owner, + repo: github.context.repo.repo, + base: previousCommit.sha, + head: latestCommit.sha + }) + + const changes: DocChange[] = [] + + for (const file of diff.data.files || []) { + if (isDocFile(file.filename)) { + const changeType = getChangeType(file.status) + const sourcePath = file.filename + const targetPath = getTargetPath(file.filename) + + changes.push({ + path: file.filename, + changeType, + sourcePath, + targetPath + }) + + console.log(`Detected ${changeType}: ${file.filename}`) + } + } + + return changes + } catch (error) { + console.error("Error detecting changes:", error) + return [] + } +} + +function isDocFile(filename: string): boolean { + return filename.startsWith(SOURCE_DOCS_PATH) && + (filename.endsWith('.mdx') || filename.endsWith('.md')) +} + +function getChangeType(status: string): "added" | "modified" | "deleted" { + switch (status) { + case 'added': return 'added' + case 'modified': return 'modified' + case 'deleted': + case 'removed': return 'deleted' + default: return 'modified' + } +} + +function getTargetPath(sourcePath: string): string { + const relativePath = sourcePath.replace(SOURCE_DOCS_PATH, '') + return path.join(TARGET_DOCS_PATH, relativePath) +} + +async function createTranslationBranch(): Promise { + console.log("Creating translation branch...") + const branchName = `auto-translate/${Date.now()}` + + await $`git checkout -b ${branchName}` + console.log(`Created branch: ${branchName}`) + + return branchName +} + +async function processDocChange(change: DocChange) { + console.log(`Processing ${change.changeType}: ${change.path}`) + + if (change.changeType === 'deleted') { + // Remove target file if source was deleted + if (fs.existsSync(change.targetPath)) { + fs.unlinkSync(change.targetPath) + console.log(`Deleted: ${change.targetPath}`) + } else { + console.log(`Target file already deleted: ${change.targetPath}`) + } + return + } + + // For added/modified files, ensure source file exists + if (!fs.existsSync(change.sourcePath)) { + console.log(`Warning: Source file not found: ${change.sourcePath}`) + return + } + + // Read source content + const sourceContent = fs.readFileSync(change.sourcePath, 'utf-8') + + // Build translation prompt + const prompt = buildTranslationPrompt(sourceContent, change.path) + + // Call AI for translation + const response = await chat(prompt) + console.log(`Translation completed for: ${change.path}`) + + // AI should have modified the file directly via agent: "build" + // Verify file was created/modified + if (!fs.existsSync(change.targetPath)) { + console.log(`Warning: Target file not created: ${change.targetPath}`) + } +} + +function buildTranslationPrompt(content: string, filename: string): string { + return `You are a professional technical translator specializing in software engineering documentation. Please translate the following English documentation to Chinese (Simplified Chinese). + +File: ${filename} +Source language: English +Target language: Chinese (Simplified Chinese) + +## Translation Requirements + +### Core Principles +1. **High Quality Translation**: This is documentation for programmers, so use professional terminology and domain-specific vocabulary +2. **Natural Expression**: Avoid mechanical AI translation - make it sound like human translation +3. **Technical Accuracy**: Preserve all technical terms, code snippets, configuration parameters, and technical references exactly +4. **Formal Tone**: Use formal "您" (you) instead of informal "你" when addressing users + +### Formatting & Structure +1. **Exact Structure**: Maintain the exact same structure and formatting (Markdown syntax, code blocks, tables, etc.) +2. **Code Preservation**: Keep all code blocks, commands, file paths, and technical references unchanged +3. **Link Preservation**: Keep all links, URLs, and file references exactly as they are +4. **Heading Hierarchy**: Maintain the same heading hierarchy and organization +5. **Table Structure**: Preserve table formatting and alignment exactly + +### Content Guidelines +1. **Technical Terms**: Use established Chinese technical terminology for software engineering concepts +2. **Professional Language**: Use formal, professional Chinese expressions appropriate for technical documentation +3. **Consistency**: Maintain consistent terminology throughout the translation +4. **Clarity**: Ensure the translated text is clear and easy to understand for Chinese developers + +### What NOT to Change +- Code blocks, commands, and technical parameters +- File paths, URLs, and links +- Configuration values and settings +- Table structures and formatting +- Markdown syntax and special characters +- Technical abbreviations and acronyms + +Please translate the following content: + +${content} + +Please use the write tool to create/update the Chinese translation file. The target path should be: ${getTargetPath(filename)} + +Remember: This is professional technical documentation for developers, so prioritize accuracy, clarity, and natural Chinese expression.` +} + +async function commitAndPushTranslations(branchName: string, commitMessage: string) { + console.log("Committing and pushing translations...") + + await $`git add ${TARGET_DOCS_PATH}` + await $`git commit -m "${commitMessage}"` + await $`git push -u origin ${branchName}` + + console.log(`Pushed branch: ${branchName}`) +} + +async function createTranslationPR(branchName: string, commitMessage: string, prTitle: string) { + console.log("Creating translation PR...") + + const pr = await octokit.rest.pulls.create({ + owner: github.context.repo.owner, + repo: github.context.repo.repo, + head: branchName, + base: github.context.ref.replace('refs/heads/', ''), + title: prTitle, + body: `This PR contains automatic translations of documentation changes. + +**Translation Details:** +- Source language: ${SOURCE_LANG} +- Target language: ${TARGET_LANG} +- AI Model: ${MODEL} + +This PR was automatically generated by the auto-translate workflow.` + }) + + console.log(`Created PR: #${pr.data.number}`) + return pr.data.number +} diff --git a/auto-translate/package.json b/auto-translate/package.json new file mode 100644 index 000000000000..d8fdf38d75cc --- /dev/null +++ b/auto-translate/package.json @@ -0,0 +1,24 @@ +{ + "name": "auto-translate-action", + "version": "1.0.0", + "description": "Auto translate documentation using AI", + "main": "index.ts", + "type": "module", + "scripts": { + "start": "bun run index.ts", + "test": "bun test" + }, + "dependencies": { + "@actions/core": "1.11.1", + "@actions/github": "6.0.1", + "@octokit/rest": "22.0.0", + "@opencode-ai/sdk": "0.5.4" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "bun-types": "latest" + }, + "engines": { + "bun": ">=1.0.0" + } +} diff --git a/auto-translate/script/publish b/auto-translate/script/publish new file mode 100755 index 000000000000..3c3f440c0e9b --- /dev/null +++ b/auto-translate/script/publish @@ -0,0 +1,41 @@ +#!/bin/bash + +# Auto-translate Action Publish Script +# This script publishes the action to GitHub Marketplace + +set -e + +echo "🚀 Publishing auto-translate action..." + +# Check if we're on main branch +if [[ $(git branch --show-current) != "main" ]]; then + echo "❌ Error: Must be on main branch to publish" + exit 1 +fi + +# Check if there are uncommitted changes +if [[ -n $(git status --porcelain) ]]; then + echo "❌ Error: There are uncommitted changes" + git status --porcelain + exit 1 +fi + +# Get current version +CURRENT_VERSION=$(node -p "require('../package.json').version") +echo "📦 Current version: $CURRENT_VERSION" + +# Build the action +echo "🔨 Building action..." +cd .. +bun run build + +# Create git tag +echo "🏷️ Creating git tag v$CURRENT_VERSION..." +git tag -a "v$CURRENT_VERSION" -m "Release version $CURRENT_VERSION" +git push origin "v$CURRENT_VERSION" + +echo "✅ Action published successfully!" +echo "📋 Next steps:" +echo " 1. Go to GitHub repository" +echo " 2. Create a new release from tag v$CURRENT_VERSION" +echo " 3. The action will be available in GitHub Marketplace" diff --git a/auto-translate/script/release b/auto-translate/script/release new file mode 100755 index 000000000000..0c9d60e8e709 --- /dev/null +++ b/auto-translate/script/release @@ -0,0 +1,54 @@ +#!/bin/bash + +# Auto-translate Action Release Script +# This script creates a new release + +set -e + +echo "🎯 Creating new release for auto-translate action..." + +# Check if version is provided +if [[ -z "$1" ]]; then + echo "❌ Error: Please provide a version number" + echo "Usage: ./script/release " + echo "Example: ./script/release 1.1.0" + exit 1 +fi + +NEW_VERSION=$1 + +# Validate version format (semver) +if [[ ! $NEW_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "❌ Error: Invalid version format. Use semantic versioning (e.g., 1.0.0)" + exit 1 +fi + +echo "📦 New version: $NEW_VERSION" + +# Update package.json version +echo "🔧 Updating package.json version..." +cd .. +npm version $NEW_VERSION --no-git-tag-version + +# Update action.yml version if it has a version field +if grep -q "version:" action.yml; then + sed -i.bak "s/version:.*/version: $NEW_VERSION/" action.yml + rm action.yml.bak +fi + +# Commit version bump +echo "💾 Committing version bump..." +git add package.json action.yml +git commit -m "chore: bump version to $NEW_VERSION" + +# Create and push tag +echo "🏷️ Creating git tag v$NEW_VERSION..." +git tag -a "v$NEW_VERSION" -m "Release version $NEW_VERSION" +git push origin "v$NEW_VERSION" + +echo "✅ Release created successfully!" +echo "📋 Next steps:" +echo " 1. Go to GitHub repository" +echo " 2. Create a new release from tag v$NEW_VERSION" +echo " 3. Add release notes describing changes" +echo " 4. Publish to GitHub Marketplace if ready" diff --git a/auto-translate/tsconfig.json b/auto-translate/tsconfig.json new file mode 100644 index 000000000000..1ae7aa42ef37 --- /dev/null +++ b/auto-translate/tsconfig.json @@ -0,0 +1,40 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "allowJs": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./", + "baseUrl": "./", + "paths": { + "@/*": ["./*"] + }, + "types": ["node", "bun-types"] + }, + "include": [ + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules", + "dist", + "**/*.test.ts", + "**/*.spec.ts" + ], + "ts-node": { + "esm": true + } +} diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 4f809a4bbed9..2ef752b0c714 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -1,6 +1,7 @@ import type { ModelMessage } from "ai" import { unique } from "remeda" + export namespace ProviderTransform { function normalizeToolCallIds(msgs: ModelMessage[]): ModelMessage[] { return msgs.map((msg) => { @@ -9,7 +10,7 @@ export namespace ProviderTransform { if ((part.type === "tool-call" || part.type === "tool-result") && "toolCallId" in part) { return { ...part, - toolCallId: part.toolCallId.replace(/[^a-zA-Z0-9_-]/g, "_"), + toolCallId: part.toolCallId.replace(/[^a-zA-Z0-9_-]/g, '_') } } return part @@ -39,7 +40,8 @@ export namespace ProviderTransform { } for (const msg of unique([...system, ...final])) { - const shouldUseContentOptions = providerID !== "anthropic" && Array.isArray(msg.content) && msg.content.length > 0 + const shouldUseContentOptions = + providerID !== "anthropic" && Array.isArray(msg.content) && msg.content.length > 0 if (shouldUseContentOptions) { const lastContent = msg.content[msg.content.length - 1] @@ -68,34 +70,17 @@ export namespace ProviderTransform { if (providerID === "anthropic" || modelID.includes("anthropic") || modelID.includes("claude")) { msgs = applyCaching(msgs, providerID) } - + return msgs } export function temperature(_providerID: string, modelID: string) { if (modelID.toLowerCase().includes("qwen")) return 0.55 - if (modelID.toLowerCase().includes("claude")) return 1 return 0 } export function topP(_providerID: string, modelID: string) { - if (modelID.toLowerCase().includes("qwen")) return 1 - return undefined - } - - export function options(providerID: string, modelID: string, sessionID: string): Record | undefined { - const result: Record = {} - - if (providerID === "openai") { - result["promptCacheKey"] = sessionID - } - - if (modelID.includes("gpt-5") && !modelID.includes("gpt-5-chat")) { - result["reasoningEffort"] = "minimal" - if (providerID !== "azure") { - result["textVerbosity"] = "low" - } - } - return result + if (modelID.toLowerCase().includes("qwen")) return 0.9 + return 1 } } diff --git a/packages/web/astro.config.mjs b/packages/web/astro.config.mjs index 350d0b31b4ab..6c422eb2ca28 100644 --- a/packages/web/astro.config.mjs +++ b/packages/web/astro.config.mjs @@ -101,10 +101,16 @@ export default defineConfig({ headerLinks: config.headerLinks, }), ], + locales: { + root: { label: "English", lang: "en" }, + zh: { label: "简体中文", lang: "zh-CN" }, + }, + defaultLocale: "root", }), ], redirects: { "/discord": "https://discord.gg/opencode", + "/.well-known/appspecific/com.chrome.devtools.json": "/", }, }) diff --git a/packages/web/src/components/Header.astro b/packages/web/src/components/Header.astro index 396200a3eb12..83f4471b0e34 100644 --- a/packages/web/src/components/Header.astro +++ b/packages/web/src/components/Header.astro @@ -8,13 +8,19 @@ import SocialIcons from 'virtual:starlight/components/SocialIcons'; import SiteTitle from '@astrojs/starlight/components/SiteTitle.astro'; const path = Astro.url.pathname; - const links = astroConfig.social || []; const headerLinks = config.headerLinks; +// Generic language detection based on URL path +const pathSegments = path.split('/'); +const currentLocale = pathSegments[1] !== 'docs' ? pathSegments[1] : 'root'; + +// Get available locales from Starlight config +const availableLocales = astroConfig.locales || {}; + --- -{ path.startsWith("/s") +{ path.startsWith("/docs") || path.startsWith("/zh/docs") || path.startsWith("/s") ?
@@ -27,6 +33,16 @@ const headerLinks = config.headerLinks; }
+ +
+ +
{ links.length > 0 && ( : } + + +