Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support auto translate to english #821

Merged
merged 16 commits into from
Dec 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion .github/workflows/docs-translate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,25 @@ on:

jobs:
translate:
env:
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9
run_install: false

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
Expand All @@ -36,14 +50,21 @@ jobs:
cd ..
rm -rf opencc4php

- name: Install Packages
run: pnpm install

- name: Setup Dependencies
run: composer install

- name: Start Translate
- name: Start Translate ZH
run: |
cp docs/index.md docs/zh-cn/index.md
php bin/doc-translate

- name: Start Translate EN
run: |
pnpm run docs:translate

Comment on lines +59 to +67
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

建议增加翻译步骤的错误处理

翻译步骤需要增加错误处理机制,确保翻译失败时能够及时发现并处理。

建议修改如下:

  - name: Start Translate ZH
  run: |
+   set -e
    cp docs/index.md docs/zh-cn/index.md
+   if ! php bin/doc-translate; then
+     echo "中文文档翻译失败"
+     exit 1
+   fi

  - name: Start Translate EN
  run: |
+   set -e
+   if ! pnpm run docs:translate; then
+     echo "英文文档翻译失败"
+     exit 1
+   fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Start Translate ZH
run: |
cp docs/index.md docs/zh-cn/index.md
php bin/doc-translate
- name: Start Translate EN
run: |
pnpm run docs:translate
- name: Start Translate ZH
run: |
set -e
cp docs/index.md docs/zh-cn/index.md
if ! php bin/doc-translate; then
echo "中文文档翻译失败"
exit 1
fi
- name: Start Translate EN
run: |
set -e
if ! pnpm run docs:translate; then
echo "英文文档翻译失败"
exit 1
fi

- name: Commit Updated
uses: stefanzweifel/git-auto-commit-action@v4
with:
Expand Down
52 changes: 52 additions & 0 deletions bin/doc-translate.github-model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import OpenAI from "openai";
import { promises as fs } from 'fs';
import path from 'path';

const token = process.env["GITHUB_TOKEN"];
const endpoint = "https://models.inference.ai.azure.com";
const modelName = "o1-mini";

async function translateFiles() {
const client = new OpenAI({
baseURL: endpoint,
apiKey: token,
dangerouslyAllowBrowser: true
});

const docsPath = path.join(process.cwd(), 'docs/zh-cn');

async function translateFile(filePath) {
const content = await fs.readFile(filePath, 'utf8');
const response = await client.chat.completions.create({
messages: [
{ role: "user", content: "You are a professional translator. Translate the following Markdown content from Chinese to English. Preserve all Markdown formatting." },
{ role: "user", content: content }
],
model: modelName
});

const translatedContent = response.choices[0].message.content;
const englishPath = filePath.replace('/zh-cn/', '/en/');
await fs.mkdir(path.dirname(englishPath), { recursive: true });
await fs.writeFile(englishPath, translatedContent);
console.log(`Translated: ${filePath} -> ${englishPath}`);
}

async function processDirectory(dirPath) {
const files = await fs.readdir(dirPath, { withFileTypes: true });

for (const file of files) {
const fullPath = path.join(dirPath, file.name);

if (file.isDirectory()) {
await processDirectory(fullPath);
} else if (file.name.endsWith('.md')) {
await translateFile(fullPath);
}
}
}

await processDirectory(docsPath);
}

translateFiles().catch(console.error);
98 changes: 58 additions & 40 deletions bin/doc-translate.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,70 @@
import OpenAI from "openai";
import { promises as fs } from 'fs';
import { readdir, readFile, writeFile, mkdir } from 'fs/promises';
import path from 'path';

const token = process.env["GITHUB_TOKEN"];
const endpoint = "https://models.inference.ai.azure.com";
const modelName = "o1-mini";
const endpoint = "https://api.deepseek.com";
const token = process.env["DEEPSEEK_API_KEY"];
const MAX_CONCURRENT = 5; // 最大并发数
const MAX_RETRIES = 3; // 最大重试次数

async function translateFiles() {
const client = new OpenAI({
const openai = new OpenAI({
baseURL: endpoint,
apiKey: token,
dangerouslyAllowBrowser: true
});
});

const docsPath = path.join(process.cwd(), 'docs/zh-cn');

async function translateFile(filePath) {
const content = await fs.readFile(filePath, 'utf8');
const response = await client.chat.completions.create({
messages: [
{ role: "user", content: "You are a professional translator. Translate the following Markdown content from Chinese to English. Preserve all Markdown formatting." },
{ role: "user", content: content }
],
model: modelName
});

const translatedContent = response.choices[0].message.content;
const englishPath = filePath.replace('/zh-cn/', '/en/');
await fs.mkdir(path.dirname(englishPath), { recursive: true });
await fs.writeFile(englishPath, translatedContent);
console.log(`Translated: ${filePath} -> ${englishPath}`);
}

async function processDirectory(dirPath) {
const files = await fs.readdir(dirPath, { withFileTypes: true });
async function translateWithRetry(content, retries = 0) {
try {
const completion = await openai.chat.completions.create({
messages: [
{ role: "system", content: "You are a professional translator. Translate the following Chinese markdown content to English. Keep all markdown formatting intact." },
{ role: "user", content: content }
],
model: "deepseek-chat",
});
return completion.choices[0].message.content;
} catch (error) {
if (retries < MAX_RETRIES) {
await new Promise(resolve => setTimeout(resolve, 1000 * (retries + 1)));
return translateWithRetry(content, retries + 1);
}
throw error;
}
}

for (const file of files) {
const fullPath = path.join(dirPath, file.name);
async function processFile(srcPath, destPath) {
const destFolder = path.dirname(destPath);
await mkdir(destFolder, { recursive: true });

const content = await readFile(srcPath, 'utf8');
const translatedContent = await translateWithRetry(content);
const finalContent = translatedContent.replace(/\/zh-cn\//g, '/en/');
await writeFile(destPath, finalContent);
console.log(`Translated: ${path.basename(srcPath)}`);
}

if (file.isDirectory()) {
await processDirectory(fullPath);
} else if (file.name.endsWith('.md')) {
await translateFile(fullPath);
}
async function translateFiles(srcDir, destDir) {
try {
const files = await readdir(srcDir, { recursive: true });
const mdFiles = files.filter(file => file.endsWith('.md'));

// 将文件分批处理
for (let i = 0; i < mdFiles.length; i += MAX_CONCURRENT) {
const batch = mdFiles.slice(i, i + MAX_CONCURRENT);
const promises = batch.map(file => {
const srcPath = path.join(srcDir, file);
const destPath = path.join(destDir, file);
return processFile(srcPath, destPath).catch(error => {
console.error(`Error translating ${file}:`, error);
});
});

await Promise.all(promises);
}

console.log('All translations completed!');
} catch (error) {
console.error('Translation error:', error);
}
}

await processDirectory(docsPath);
}

translateFiles().catch(console.error);
translateFiles('docs/zh-cn', 'docs/en');
2 changes: 1 addition & 1 deletion docs/en/components/amqp-job.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Introduction

`friendsofhyperf/amqp-job` is an asynchronous task component based on the `hyperf/amqp` component. It supports dispatching tasks to an AMQP service, which are then consumed by consumers. By encapsulating the `hyperf/amqp` component, it provides a more convenient way to dispatch and consume tasks.
`friendsofhyperf/amqp-job` is an asynchronous task component based on the `hyperf/amqp` component, which supports dispatching tasks to the AMQP service and then consuming them via consumers. It encapsulates the `hyperf/amqp` component, providing a more convenient way to dispatch and consume tasks.

## Installation

Expand Down
14 changes: 7 additions & 7 deletions docs/en/components/cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ composer require friendsofhyperf/cache

## Usage

### Annotations
### Annotation

```php
namespace App\Controller;
Expand All @@ -29,7 +29,7 @@ class IndexController
public function index()
{
return $this->cache->remember($key, $ttl=60, function() {
// return sth
// Return value
});
}
}
Expand All @@ -41,7 +41,7 @@ class IndexController
use FriendsOfHyperf\Cache\Facade\Cache;

Cache::remember($key, $ttl=60, function() {
// return sth
// Return value
});
```

Expand All @@ -52,14 +52,14 @@ use FriendsOfHyperf\Cache\Facade\Cache;
use FriendsOfHyperf\Cache\CacheManager;

Cache::store('co')->remember($key, $ttl=60, function() {
// return sth
// Return value
});

di(CacheManager::class)->store('co')->remember($key, $ttl=60, function() {
// return sth
// Return value
});
```

## References
## Reference

Like [Laravel-Cache](https://laravel.com/docs/8.x/cache)
For more information, please refer to [Laravel-Cache](https://laravel.com/docs/8.x/cache)
4 changes: 2 additions & 2 deletions docs/en/components/command-signals.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Command Signals

Signal handling component for Hyperf Command.
Hyperf Command Signal Handling Component.

## Installation

Expand Down Expand Up @@ -47,7 +47,7 @@ class FooCommand extends HyperfCommand
}
```

## Running
## Execution

- `Ctrl + C`

Expand Down
4 changes: 2 additions & 2 deletions docs/en/components/command-validation.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Command Validation

A validation component for Hyperf command line.
A validation component for Hyperf command-line applications.

## Installation

Expand All @@ -27,7 +27,7 @@ class FooCommand extends HyperfCommand
use ValidatesInput;

/**
* The command line to execute
* The command line to be executed.
*/
protected string $name = 'foo:hello {?name : The name of the person to greet.}';

Expand Down
24 changes: 12 additions & 12 deletions docs/en/components/compoships.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Compoships

**Compoships** provides the ability to define relationships based on two (or more) columns in Hyperf's Model ORM. When dealing with third-party or pre-existing schemas/databases, there is often a need to match multiple columns in the definition of Eloquent relationships.
**Compoships** provides the ability to specify relationships based on two (or more) columns in Hyperf's Model ORM. This is particularly useful when dealing with third-party or pre-existing schemas/databases where you need to match multiple columns in the definition of Eloquent relationships.

## Problem
## The Problem

Eloquent does not support composite keys. Therefore, it is not possible to define relationships from one model to another by matching multiple columns. Attempting to use `where` clauses (as shown in the example below) does not work when eager loading relationships because **$this->team_id** is null when handling the relationship.
Eloquent does not support composite keys. Therefore, it is not possible to define a relationship from one model to another by matching multiple columns. Attempting to use a `where` clause (as shown in the example below) does not work when eager loading relationships because **$this->team_id** is null when the relationship is being processed.

```php
namespace App;
Expand All @@ -23,7 +23,7 @@ class User extends Model

## Installation

It is recommended to install the **Compoships** component using [Composer](http://getcomposer.org/).
It is recommended to install **Compoships** via [Composer](http://getcomposer.org/).

```shell
composer require friendsofhyperf/compoships
Expand All @@ -33,17 +33,17 @@ composer require friendsofhyperf/compoships

### Using the `FriendsOfHyperf\Compoships\Database\Eloquent\Model` Class

Simply have your model classes extend the `FriendsOfHyperf\Compoships\Database\Eloquent\Model` base class. `FriendsOfHyperf\Compoships\Database\Eloquent\Model` extends the `Eloquent` base class without altering its core functionality.
Simply have your model class extend the `FriendsOfHyperf\Compoships\Database\Eloquent\Model` base class. `FriendsOfHyperf\Compoships\Database\Eloquent\Model` extends the `Eloquent` base class without altering its core functionality.

### Using the `FriendsOfHyperf\Compoships\Compoships` Trait

If for some reason you cannot extend `FriendsOfHyperf\Compoships\Database\Eloquent\Model` in your model, you can use the `FriendsOfHyperf\Compoships\Compoships` trait. Simply use this trait in your model.
If for some reason you cannot extend your model from `FriendsOfHyperf\Compoships\Database\Eloquent\Model`, you can utilize the `FriendsOfHyperf\Compoships\Compoships` trait. Just use the trait in your model.

**Note:** To define a multi-column relationship from model *A* to another model *B*, **both models must extend `FriendsOfHyperf\Compoships\Database\Eloquent\Model` or use the `FriendsOfHyperf\Compoships\Compoships` trait**

### Usage

... Now we can define relationships from model *A* to another model *B* by matching two or more columns (by passing an array of columns instead of a string).
... Now we can define a relationship from model *A* to another model *B* by matching two or more columns (by passing an array of columns instead of a string).

```php
namespace App;
Expand All @@ -61,7 +61,7 @@ class A extends Model
}
```

We can use the same syntax to define the inverse relationship:
We can use the same syntax to define the inverse of the relationship:

```php
namespace App;
Expand All @@ -81,15 +81,15 @@ class B extends Model

### Example

As an example, suppose we have a task list with categories, managed by multiple user teams, where:
As an example, let's assume we have a task list with categories, managed by multiple user teams, where:

- A task belongs to a category
- A task is assigned to a team
- A team has many users
- A user belongs to a team
- A user is responsible for a category's tasks
- A user is responsible for tasks in a category

The user responsible for a specific task is the currently responsible user for that category within the team.
The user responsible for a specific task is the user currently responsible for that category within the assigned team.

```php
namespace App;
Expand All @@ -107,7 +107,7 @@ class User extends Model
}
```

The same syntax can be used to define the inverse relationship:
The same syntax can be used to define the inverse of the relationship:

```php
namespace App;
Expand Down
Loading