-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
263 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# 質問文 | ||
|
||
これから入力する文章はNext.jsに関する記事です。 | ||
この記事を「条件」のセクションの項目に従って日本語に翻訳してください。 | ||
なお、入力はマークダウンを拡張したmdx形式で、出力もmdx形式を維持します。 | ||
例えばリンク、コードブロックもマークダウンで出力してください。 | ||
|
||
|
||
# 条件 | ||
|
||
- 文章はWebアプリケーション開発に関するものです。Webアプリケーションの文脈で翻訳してください | ||
- コードブロックに付随する「filename=」は「title=」に置換してください | ||
- コードブロック内のコードは翻訳せず、コメント部分だけ翻訳してください | ||
- リストの末尾の句読点は省きます | ||
- テーブル内の文章の末尾の句読点は省きます | ||
- 文末のセミコロンは全角に変換してください | ||
- text-lint の"preset-bengo4"のルールに沿った文章に翻訳してください | ||
- リンクのパスが「/docs/app」から始まる場合、「/docs/app-router」に置き換えてください | ||
- 「Good to know」は訳さずにそのまま出力してください | ||
- 「Version History」は「バージョン履歴」に翻訳してください | ||
- <PagesOnly></PagesOnly>で囲まれた部分は取り除いてください | ||
|
||
# 出力フォーマット | ||
|
||
- mdx形式 | ||
- ただし、出力結果を```mdx~```で囲まないでください |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import OpenAI from 'openai' | ||
import pLimit from 'p-limit' | ||
import path, { basename, dirname } from 'node:path' | ||
import process from 'node:process' | ||
import { argv, spinner, fs } from 'zx' | ||
|
||
import { | ||
parseMdxDiff, | ||
PROJECT_ROOT, | ||
MdxDiff, | ||
createLogger, | ||
parseArgs, | ||
} from './utils.mts' | ||
|
||
const defaults = { | ||
apiKey: process.env.OPENAI_API_KEY, | ||
concurrency: 5, | ||
lang: 'ja', | ||
promptDir: path.join(import.meta.dirname, `prompt`), | ||
nextjsDir: path.join(PROJECT_ROOT, 'next.js'), | ||
} as const | ||
|
||
const log = createLogger(basename(import.meta.filename)) | ||
|
||
const limit = pLimit(defaults.concurrency) | ||
|
||
log('important', '🚀 translate started.') | ||
|
||
const argsParseResult = parseArgs({ | ||
description: `Translate files based on diff. The default language is "${defaults.lang}". `, | ||
usage: `tsx translate.mts [options] <diff-file-path> `, | ||
parser: (args) => { | ||
if (args._.length !== 1) { | ||
return { | ||
success: false, | ||
message: 'One diff file path must be specified as an argument.', | ||
} | ||
} | ||
const [diffFilePath] = args._ | ||
|
||
let lang: string | ||
if (!args.l) { | ||
lang = defaults.lang | ||
} else if (typeof args.l !== 'string') { | ||
return { | ||
success: false, | ||
message: '"-l" option value must be a string.', | ||
} | ||
} else { | ||
lang = args.l | ||
} | ||
|
||
return { | ||
success: true, | ||
args: { | ||
diffFilePath, | ||
lang, | ||
}, | ||
} | ||
}, | ||
})(argv) | ||
|
||
if (!argsParseResult.success) { | ||
log('error', argsParseResult.message) | ||
process.exit(1) | ||
} | ||
|
||
const { args } = argsParseResult | ||
|
||
const diffList = await parseMdxDiff( | ||
path.isAbsolute(args.diffFilePath) | ||
? args.diffFilePath | ||
: path.resolve(args.diffFilePath) | ||
) | ||
|
||
const command = await (async () => { | ||
const openai = new OpenAI({ | ||
apiKey: defaults.apiKey, | ||
}) | ||
|
||
const systemContent = ( | ||
await fs.readFile(path.resolve(defaults.promptDir, `${args.lang}.md`)) | ||
).toString() | ||
|
||
const translate = async (mdxFilePath: string) => { | ||
const userContent = ( | ||
await fs.readFile(path.resolve(defaults.nextjsDir, mdxFilePath)) | ||
).toString() | ||
|
||
log('normal', `requesting to OpenAI to translate "${mdxFilePath}"`) | ||
const result = await openai.chat.completions.create({ | ||
model: 'gpt-4o', | ||
messages: [ | ||
{ role: 'system', content: systemContent }, | ||
{ role: 'user', content: userContent }, | ||
], | ||
stream: false, | ||
}) | ||
|
||
if (result.choices.length !== 1) { | ||
throw new Error( | ||
`invalid OpenAI translation result: ${JSON.stringify(result)}` | ||
) | ||
} | ||
const translatedContent = result.choices[0].message.content | ||
|
||
const pathToWrite = path.resolve(PROJECT_ROOT, mdxFilePath) | ||
log('normal', `writing translated files to "${pathToWrite}"`) | ||
await fs.ensureDir(dirname(pathToWrite)) | ||
|
||
await fs.writeFile(pathToWrite, translatedContent ?? '') | ||
} | ||
|
||
const rm = async (mdxFilePath: string) => { | ||
const filePath = path.resolve(PROJECT_ROOT, mdxFilePath) | ||
log('normal', `deleting... ${filePath}`) | ||
return await fs.unlink(filePath) | ||
} | ||
|
||
return async (diff: MdxDiff) => { | ||
const { status } = diff | ||
|
||
switch (status) { | ||
case 'A': | ||
case 'M': | ||
return translate(diff.filePath) | ||
case 'D': | ||
return rm(diff.filePath) | ||
case 'R': { | ||
await rm(diff.fromPath) | ||
return translate(diff.toPath) | ||
} | ||
default: | ||
throw new Error(`NOT supported diff : ${JSON.stringify(diff)}`) | ||
} | ||
} | ||
})() | ||
|
||
const commands = diffList.map((diff) => limit(() => command(diff))) | ||
|
||
await spinner(() => Promise.all(commands)) | ||
|
||
log('important', '🎉 translate finished successfully!') |
Oops, something went wrong.