Skip to content

Commit 2e443f4

Browse files
authored
refactor: improve gemini translator (angular#1006)
1 parent 8733d0e commit 2e443f4

File tree

9 files changed

+331
-388
lines changed

9 files changed

+331
-388
lines changed

.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
GOOGLE_API_KEY=

adev-ja/src/content/introduction/essentials/components.md

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<docs-decorative-header title="コンポーネント" imgSrc="adev/src/assets/images/components.svg"> <!-- markdownlint-disable-line -->
2-
Angularでアプリケーションを作成するための基本的な構成要素
2+
Angularアプリケーションを作成するための基本的な構成要素
33
</docs-decorative-header>
44

55
コンポーネントは、Angularアプリケーションの主要な構成要素です。各コンポーネントは、より大きなウェブページの一部を表します。アプリケーションをコンポーネントに整理することで、プロジェクトに構造が与えられ、コードが特定の部分に明確に分割されるため、保守と拡張が容易になります。
@@ -73,7 +73,7 @@ h1 {
7373

7474
## コンポーネントの使用
7575

76-
複数のコンポーネントを組み合わせてアプリケーションを構築します。例えば、ユーザープロフィールページを構築する場合、ページを次のような複数のコンポーネントに分割できます
76+
アプリケーションは複数のコンポーネントを組み合わせて構築します。例えば、ユーザーのプロファイルページを作成する場合、次のようにページをいくつかのコンポーネントに分割できます
7777

7878
```mermaid
7979
flowchart TD
@@ -84,14 +84,14 @@ flowchart TD
8484
D[UserAddress]
8585
```
8686

87-
ここでは`UserProfile`コンポーネントは他のいくつかのコンポーネントを使用して最終的なページを作成します。
87+
ここで`UserProfile`コンポーネントは他のいくつかのコンポーネントを使用して最終的なページを作成します。
8888

8989
コンポーネントをインポートして使用するには、次の手順が必要です。
90-
1. コンポーネントのTypeScriptファイルで、使用するコンポーネントの`import`文を追加します。
91-
2. `@Component`デコレーターで、使用するコンポーネントの`imports`配列にエントリを追加します
92-
3. コンポーネントのテンプレートで、使用するコンポーネントのセレクターと一致する要素を追加します。
90+
1. コンポーネントのTypeScriptファイルに、使用するコンポーネントの`import`文を追加します。
91+
2. `@Component`デコレーターの`imports`配列に、使用するコンポーネントのエントリを追加します
92+
3. コンポーネントのテンプレートに、使用するコンポーネントのセレクターと一致する要素を追加します。
9393

94-
`UserProfile`コンポーネントが`ProfilePhoto`コンポーネントをインポートする例を以下に示します
94+
`ProfilePhoto`コンポーネントをインポートする`UserProfile`コンポーネントの例を次に示します
9595

9696
```angular-ts
9797
// user-profile.ts
@@ -111,7 +111,7 @@ export class UserProfile {
111111
}
112112
```
113113

114-
Tip: Angularコンポーネントについてもっと知りたいですか? 詳細については[詳細なコンポーネントガイド](guide/components)を参照してください。
114+
Tip: Angularコンポーネントの詳細については[詳細なコンポーネントガイド](guide/components)を参照してください。
115115

116116
## 次の手順
117117

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
"test": "yarn test:patch",
1414
"test:patch": "git apply -v --check --directory origin ./tools/adev-patches/*.patch",
1515
"update-origin": "tsx tools/update-origin.ts",
16-
"translate": "tsx tools/translate.ts"
16+
"translate": "tsx --env-file=.env tools/translator/main.ts"
1717
},
1818
"packageManager": "[email protected]",
1919
"devDependencies": {
20-
"@google/generative-ai": "0.14.1",
20+
"@google/generative-ai": "0.21.0",
2121
"@types/node": "20.14.10",
2222
"chokidar": "3.6.0",
2323
"consola": "3.2.3",
@@ -32,6 +32,6 @@
3232
"textlint-rule-preset-ja-spacing": "2.4.3",
3333
"textlint-rule-preset-ja-technical-writing": "10.0.1",
3434
"textlint-rule-prh": "^6.0.0",
35-
"tsx": "^4.16.2"
35+
"tsx": "4.19.2"
3636
}
3737
}

tools/translate.ts

-225
This file was deleted.

tools/translator/main.ts

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import consola from 'consola';
2+
import assert from 'node:assert';
3+
import { readFile, writeFile } from 'node:fs/promises';
4+
import { resolve } from 'node:path';
5+
import { parseArgs } from 'node:util';
6+
import {
7+
cpRf,
8+
exists,
9+
getEnFilePath,
10+
getLocalizedFilePath,
11+
} from '../lib/fsutils';
12+
import { rootDir } from '../lib/workspace';
13+
import { GeminiTranslator } from './translate';
14+
15+
async function main() {
16+
const apiKey = process.env.GOOGLE_API_KEY;
17+
assert(apiKey, 'GOOGLE_API_KEY 環境変数が設定されていません。');
18+
19+
const args = parseArgs({
20+
options: { write: { type: 'boolean', default: false, short: 'w' } },
21+
allowPositionals: true,
22+
});
23+
const { write } = args.values;
24+
const [file] = args.positionals;
25+
26+
const fileExists = await exists(file);
27+
if (!fileExists) {
28+
throw new Error(`ファイルが見つかりません: ${file}`);
29+
}
30+
31+
const content = await readFile(file, 'utf-8');
32+
const prh = await readFile(resolve(rootDir, 'prh.yml'), 'utf-8');
33+
34+
const translator = new GeminiTranslator(apiKey);
35+
const translated = await translator.translate(content, prh);
36+
37+
console.log(translated);
38+
await writeTranslatedContent(file, translated, write);
39+
}
40+
41+
async function writeTranslatedContent(
42+
file: string,
43+
content: string,
44+
forceWrite = false
45+
) {
46+
const outputFile = getLocalizedFilePath(file);
47+
const save =
48+
forceWrite ||
49+
(await consola.prompt(`翻訳結果を保存しますか?\n保存先: ${outputFile}`, {
50+
type: 'confirm',
51+
initial: false,
52+
}));
53+
if (!save) {
54+
return;
55+
}
56+
57+
const enFile = getEnFilePath(file);
58+
// .en.* が存在しない場合は原文コピーを忘れているため、.en.md に元ファイルをコピーする
59+
if (!(await exists(enFile))) {
60+
consola.warn(
61+
`原文ファイルが見つかりません。入力ファイルを ${enFile} にコピーします。`
62+
);
63+
await cpRf(file, enFile);
64+
}
65+
await writeFile(outputFile, content);
66+
consola.success(`保存しました`);
67+
}
68+
69+
main().catch((error) => {
70+
consola.error(error);
71+
process.exit(1);
72+
});

tools/translator/markdown.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export type ContentBlock = string;
2+
3+
export function splitMarkdown(content: string): ContentBlock[] {
4+
// split content by heading lines (lines starting with ##)
5+
return content.split(/\n(?=##\s)/);
6+
}
7+
8+
export async function renderMarkdown(blocks: ContentBlock[]) {
9+
const content = blocks
10+
.map((block) => (block.endsWith('\n') ? block.replace(/\n$/, '') : block))
11+
.join('\n\n');
12+
// add trailing newline
13+
return content + '\n';
14+
}

0 commit comments

Comments
 (0)