-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path@translate.ts
More file actions
95 lines (79 loc) · 2.56 KB
/
@translate.ts
File metadata and controls
95 lines (79 loc) · 2.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import { readFile, writeFile } from "node:fs/promises"
import { openai } from "@ai-sdk/openai"
import { tasks } from "@clack/prompts"
import { generateObject } from "ai"
import dotenv from "dotenv"
import { execa } from "execa"
import { po } from "gettext-parser"
import { objectKeys } from "ts-extras"
import { z } from "zod"
import { LanguageIdDefault, Languages } from "#constants/language.ts"
import type { LanguageId } from "#constants/language.ts"
process.chdir(import.meta.dirname)
dotenv.config()
const $ = execa({
stdout: ["inherit", "pipe"],
verbose: "short",
preferLocal: true,
})
await $`lingui extract`
await translateLanguages()
// Handled by Vite
// await $`lingui compile`
async function translateLanguages() {
await tasks(
objectKeys(Languages).map(languageId => ({
title: `Translating ${languageId}`,
task: () => translateLanguage(languageId),
})),
)
}
// TODO: extract empty messages and translate using JSON/JSONSchema
async function translateLanguage(languageId: LanguageId) {
if (languageId === LanguageIdDefault) return
const path = `locales/${languageId}/messages.po`
const source = await readFile(path)
const pofile = po.parse(source)
const translations = pofile.translations[""]
if (!translations) {
return
}
const missingTranslations = Object.values(translations).filter(
message => message.msgstr && !message.msgstr.filter(Boolean).length,
)
if (!missingTranslations.length) {
return
}
const result = await generateObject({
model: openai("gpt-4.1"),
schema: z.object({
translations: z.array(
z.object({
msgid: z.string(),
msgid_plural: z.string().optional(),
msgstr: z.array(z.string()),
}),
),
}),
prompt: `
You are a professional translator. Here is untranslated PO (gettext) translations.
Translate them from **${LanguageIdDefault}** to **${languageId}**.
- Keep all msgid values unchanged
- Preserve all placeholders like {variable}, %s, {{name}}, etc.
- Maintain the exact JSON format
PO (gettext) translations:
${JSON.stringify(missingTranslations, null, 2)}
`,
})
// Update added translations
for (const translatedMessage of result.object.translations) {
const missingMessage = translations[translatedMessage.msgid]
if (missingMessage) {
missingMessage.msgstr = translatedMessage.msgstr
}
}
// Delete removed translations
pofile.obsolete = {}
const target = po.compile(pofile, { foldLength: Number.POSITIVE_INFINITY })
await writeFile(path, target)
}