7
7
*/
8
8
9
9
import { GoogleGenAI } from '@google/genai' ;
10
+ import { SingleBar } from 'cli-progress' ;
11
+ import { consola } from 'consola' ;
10
12
import { setTimeout } from 'node:timers/promises' ;
11
- import { renderMarkdown , splitMarkdown } from './markdown' ;
13
+ import {
14
+ ContentType ,
15
+ getContentType ,
16
+ renderContent ,
17
+ splitMarkdown ,
18
+ splitRecommendations ,
19
+ } from './utils' ;
12
20
13
21
export class GeminiTranslator {
14
22
readonly #client: GoogleGenAI ;
@@ -20,20 +28,82 @@ export class GeminiTranslator {
20
28
console . log ( `Using model: ${ model } ` ) ;
21
29
}
22
30
23
- async translate ( content : string , prh : string ) : Promise < string > {
24
- const systemInstruction = `
31
+ async translate (
32
+ filename : string ,
33
+ content : string ,
34
+ prh : string
35
+ ) : Promise < string > {
36
+ const contentType = getContentType ( filename ) ;
37
+ const systemInstruction = getSystemInstruction ( contentType , prh ) ;
38
+
39
+ const chat = this . #client. chats . create ( {
40
+ model : this . #model,
41
+ config : { systemInstruction, temperature : 0.1 } ,
42
+ } ) ;
43
+
44
+ consola . start ( `Starting translation for ${ filename } ` ) ;
45
+ await chat
46
+ . sendMessage ( {
47
+ message : [
48
+ `これから ${ filename } の翻訳作業を開始します。次のメッセージからテキスト断片を入力するので、日本語に翻訳して出力してください。今回の翻訳タスクと遵守するルールをおさらいしてください。` ,
49
+ ] ,
50
+ } )
51
+ . then ( ( response ) => {
52
+ if ( response . text ) {
53
+ consola . info ( `Gemini: ${ response . text } ` ) ;
54
+ }
55
+ } ) ;
56
+
57
+ const progress = new SingleBar ( { } ) ;
58
+
59
+ const blocks =
60
+ contentType === 'markdown'
61
+ ? splitMarkdown ( content )
62
+ : splitRecommendations ( content ) ;
63
+
64
+ progress . start ( blocks . length , 0 ) ;
65
+ const rpm = 10 ; // Requests per minute
66
+ const waitTime = Math . floor ( ( 60 * 1000 ) / rpm ) ;
67
+
68
+ const translated = [ ] ;
69
+ for ( const block of blocks ) {
70
+ const prompt = block . trim ( ) ;
71
+ const delay = setTimeout ( waitTime ) ;
72
+ const response = await chat . sendMessage ( { message : [ prompt ] } ) ;
73
+ translated . push ( response . text ?? '' ) ; // Fallback in case of no response
74
+
75
+ progress . increment ( ) ;
76
+ await delay ; // Avoid rate limiting
77
+ }
78
+
79
+ progress . stop ( ) ;
80
+ return renderContent ( contentType , translated ) ;
81
+ }
82
+ }
83
+
84
+ function getSystemInstruction ( contentType : ContentType , prh : string ) : string {
85
+ return `
25
86
あなたはオープンソースライブラリの開発者向けドキュメントの翻訳者です。
26
87
入力として与えられたテキストに含まれる英語を日本語に翻訳します。
27
88
89
+ ## Task
90
+
91
+ ユーザーはテキスト全体を分割し、断片ごとに翻訳を依頼します。
92
+ あなたは与えられた断片を日本語に翻訳し、翻訳結果だけを出力します。
93
+ 前回までの翻訳結果を参照しながら、テキスト全体での表現の一貫性を保つようにしてください。
94
+
95
+ ${ ( contentType === 'markdown'
96
+ ? `
28
97
## Rules
29
98
翻訳は次のルールに従います。
30
99
31
- - 見出しレベル("#")の数を必ず維持する。
32
- - 例: "# Security" → "# セキュリティ"
100
+ - Markdownの構造の変更は禁止されています。
101
+ - 見出しレベル("#")の数を必ず維持する。
102
+ - 例: "# Security" → "# セキュリティ"
103
+ - 改行やインデントの数を必ず維持する。
33
104
- トップレベル("<h1>")以外の見出しに限り、元の見出しをlower caseでハイフン結合したアンカーIDとして使用する
34
105
- 例: "# Security" → "# セキュリティ"
35
106
- 例: "## How to use Angular" → "## Angularの使い方 {#how-to-use-angular}"
36
- - 改行やインデントの数を必ず維持する。
37
107
- 英単語の前後にスペースを入れない。
38
108
- bad: "Angular の使い方"
39
109
- good: "Angularの使い方"
@@ -43,18 +113,6 @@ export class GeminiTranslator {
43
113
- 冗長な表現を避け、自然な日本語にする。
44
114
- 例: 「することができます」→「できます」
45
115
46
- 表記揺れや不自然な日本語を避けるため、YAML形式で定義されているPRH(proofreading helper)ルールを使用して、翻訳後のテキストを校正します。
47
- 次のPRHルールを使用してください。
48
- ---
49
- ${ prh }
50
- ---
51
-
52
- ## Task
53
-
54
- ユーザーはテキスト全体を分割し、断片ごとに翻訳を依頼します。
55
- あなたは与えられた断片を日本語に翻訳し、Markdown形式で出力します。
56
- 前回の翻訳結果を参照しながら、テキスト全体での表現の一貫性を保つようにしてください。
57
-
58
116
入力例:
59
117
60
118
---
@@ -72,41 +130,77 @@ It doesn't cover application-level security, such as authentication and authoriz
72
130
このトピックでは、クロスサイトスクリプティング攻撃などの一般的なWebアプリケーションの脆弱性や攻撃に対する、Angularの組み込みの保護について説明します。
73
131
認証や認可など、アプリケーションレベルのセキュリティは扱いません。
74
132
---
133
+ `
134
+ : contentType === 'recommendations'
135
+ ? `
136
+ recommendations.tsは次のような形式のオブジェクトを含むTypeScriptファイルです。
75
137
138
+ ---
139
+ export const RECOMMENDATIONS: Step[] = [
140
+ {
141
+ possibleIn: 200,
142
+ necessaryAsOf: 400,
143
+ level: ApplicationComplexity.Basic,
144
+ step: 'Extends OnInit',
145
+ action:
146
+ "Ensure you don't use \`extends OnInit\`, or use \`extends\` with any lifecycle event. Instead use \`implements <lifecycle event>.\`",
147
+ },
148
+ {
149
+ possibleIn: 200,
150
+ necessaryAsOf: 400,
151
+ level: ApplicationComplexity.Advanced,
152
+ step: 'Deep Imports',
153
+ action:
154
+ 'Stop using deep imports, these symbols are now marked with ɵ and are not part of our public API.',
155
+ },
156
+ ---
76
157
77
- ` . trim ( ) ;
78
-
79
- const chat = this . #client . chats . create ( {
80
- model : this . #model ,
81
- config : {
82
- systemInstruction ,
83
- temperature : 0.1 ,
84
- } ,
85
- } ) ;
158
+ ## Rules
159
+ 翻訳は次のルールに従います。
160
+ - **翻訳対象となるのは "action" フィールドの文字列リテラルのみです。**
161
+ - 原文に存在しない行を追加することは禁止されています。
162
+ - 原文に存在する行を削除することは禁止されています。
163
+ - ソースコードの構造の変更は禁止されています。
164
+ - コードのロジックや構造の変更は禁止されています。
165
+ - ソースコードのキーワードや構文の翻訳は禁止されています。
166
+ - 変数名や関数名の翻訳は禁止されています。
86
167
87
- await chat . sendMessage ( {
88
- message : [
89
- `これから翻訳作業を開始します。テキスト断片を入力するので、日本語に翻訳して出力してください。` ,
90
- ] ,
91
- } ) ;
168
+ 入力例:
169
+ ---
170
+ export const RECOMMENDATIONS: Step[] = [
171
+ {
172
+ possibleIn: 200,
173
+ necessaryAsOf: 400,
174
+ level: ApplicationComplexity.Basic,
175
+ step: 'Extends OnInit',
176
+ action:
177
+ "Ensure you don't use \`extends OnInit\`, or use \`extends\` with any lifecycle event. Instead use \`implements <lifecycle event>.\`",
178
+ },
179
+ ---
92
180
93
- const blocks = splitMarkdown ( content ) ;
94
- const translated = [ ] ;
181
+ 出力例:
182
+ ---
183
+ export const RECOMMENDATIONS: Step[] = [
184
+ {
185
+ possibleIn: 200,
186
+ necessaryAsOf: 400,
187
+ level: ApplicationComplexity.Basic,
188
+ step: 'Extends OnInit',
189
+ action:
190
+ '\`OnInit\`を継承しない、あるいはライフサイクルイベントを使用する場合は\`implements <lifecycle event>\`を使用してください。',
191
+ },
192
+ ---
95
193
96
- for ( const block of blocks ) {
97
- const prompt = block . trim ( ) ;
98
- const response = await chat . sendMessage ( {
99
- message : [ prompt ] ,
100
- } ) ;
194
+ `
195
+ : ``
196
+ ) . trim ( ) } ;
101
197
102
- if ( response . text ) {
103
- translated . push ( response . text ) ;
104
- } else {
105
- translated . push ( '' ) ; // Fallback in case of no response
106
- }
198
+ ## 翻訳後の校正
107
199
108
- await setTimeout ( 3000 ) ; // Rate limiting
109
- }
110
- return renderMarkdown ( translated ) ;
111
- }
200
+ 表記揺れや不自然な日本語を避けるため、YAML形式で定義されているPRH(proofreading helper)ルールを使用して、翻訳後のテキストを校正します。
201
+ 次のPRHルールを使用してください。
202
+ ---
203
+ ${ prh }
204
+ ---
205
+ ` . trim ( ) ;
112
206
}
0 commit comments