Skip to content

Commit

Permalink
add translate
Browse files Browse the repository at this point in the history
  • Loading branch information
ytori committed Sep 20, 2024
1 parent 6e98d57 commit dfcc48b
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 114 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/auto-translation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ jobs:
node-version-file: 'package.json'
- run: npm ci
- run: npm run translate:diff
- run: npm run translate:rm
- run: npm run translate:jp
- run: npm run build
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
"lint": "npm run lint:docs",
"lint:docs": "textlint --fix docs/**",
"translate:diff": "tsx ./scripts/translation/diff-docs.mts -o .translation-diff.txt",
"translate:jp": "tsx ./scripts/translation/translate-mdx.ts",
"translate:rm": "tsx ./scripts/translation/sync-deleted.mts .translation-diff.txt"
"translate:jp": "tsx ./scripts/translation/translate.mts -l ja .translation-diff.txt"
},
"dependencies": {
"@docusaurus/core": "3.2.0",
Expand Down
58 changes: 44 additions & 14 deletions scripts/translation/diff-docs.mts
Original file line number Diff line number Diff line change
@@ -1,18 +1,43 @@
import { $, minimist } from 'zx'
import { $, argv, fs } from 'zx'
import path, { basename } from 'node:path'
import process from 'node:process'
import { createLogger, writeFile } from './utils.mts'
import { createLogger, parseArgs } from './utils.mts'

const log = createLogger(basename(import.meta.url))

const args = minimist(process.argv.slice(2), {
string: ['o'],
alias: {
o: 'output',
log('important', `🚀 diff docs started`)

const argsParseResult = parseArgs({
description: `Diff docs in "next.js" repository. If you want to save diff to files, please specify "-o <file-path>".`,
usage: `tsx diff-docs.mts [options]`,
parser: (args) => {
let outputFilePath: string | undefined
if (args.o == null) {
outputFilePath = undefined
} else if (typeof args.o === 'string') {
outputFilePath = args.o
} else {
return {
success: false,
message: '"-o" option value must be a string.',
}
}

return {
success: true,
args: {
outputFilePath,
},
}
},
}) as { output?: string }
})(argv)

log('important', `🚀 diff docs started`)
if (!argsParseResult.success) {
log('error', argsParseResult.message)
process.exit(1)
}

const { args } = argsParseResult

await $`git submodule update --remote`
const submoduleDiffOutput = await $`git diff -- next.js`
Expand Down Expand Up @@ -46,12 +71,17 @@ docDiffLines.forEach((line) => {
log('normal', `diff: ${line}`)
})

if (args.output) {
const outputFilePath = path.isAbsolute(args.output)
? args.output
: path.resolve(args.output)
await writeFile(outputFilePath, docDiffLines)
log('important', `wrote to ${outputFilePath}`)
if (args.outputFilePath) {
const pathToWrite = path.isAbsolute(args.outputFilePath)
? args.outputFilePath
: path.resolve(args.outputFilePath)

const content = docDiffLines.reduce((prev, current, index) => {
return `${prev}${index === 0 ? '' : '\n'}${current}`
}, '')

log('important', `writing diffs to ${pathToWrite}`)
await fs.writeFile(pathToWrite, content)
}

log('important', '🎉 diff docs finished successfully')
26 changes: 26 additions & 0 deletions scripts/translation/prompt/ja.md
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~```で囲まないでください
47 changes: 0 additions & 47 deletions scripts/translation/sync-deleted.mts

This file was deleted.

143 changes: 143 additions & 0 deletions scripts/translation/translate.mts
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!')
Loading

0 comments on commit dfcc48b

Please sign in to comment.