mirror of
https://github.com/coaidev/coai.git
synced 2025-05-19 21:10:18 +09:00
feat: update auto translation tool
This commit is contained in:
parent
70cff90344
commit
e3c537ddd9
3
app/.gitignore
vendored
3
app/.gitignore
vendored
@ -23,3 +23,6 @@ dev-dist
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Libre
|
||||
db
|
||||
|
@ -80,14 +80,18 @@ function SettingsDialog() {
|
||||
}
|
||||
>
|
||||
<SelectTrigger className={`select`}>
|
||||
<SelectValue placeholder={langsProps[i18n.language]} />
|
||||
<SelectValue
|
||||
placeholder={langsProps[i18n.language]}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Object.entries(langsProps).map(([key, value], idx) => (
|
||||
{Object.entries(langsProps).map(
|
||||
([key, value], idx) => (
|
||||
<SelectItem key={idx} value={key}>
|
||||
{value}
|
||||
</SelectItem>
|
||||
))}
|
||||
),
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
@ -1,9 +1,9 @@
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import { getMemory, setMemory } from "@/utils/memory.ts";
|
||||
import cn from '@/resources/i18n/cn.json';
|
||||
import en from '@/resources/i18n/en.json';
|
||||
import ru from '@/resources/i18n/ru.json';
|
||||
import cn from "@/resources/i18n/cn.json";
|
||||
import en from "@/resources/i18n/en.json";
|
||||
import ru from "@/resources/i18n/ru.json";
|
||||
|
||||
// the translations
|
||||
// (tip move them in a JSON file and import them,
|
||||
|
51
app/src/translator/adapter.ts
Normal file
51
app/src/translator/adapter.ts
Normal file
@ -0,0 +1,51 @@
|
||||
// format language code to name/ISO 639-1 code map
|
||||
const languageTranslatorMap = {
|
||||
cn: "zh-CN",
|
||||
en: "en",
|
||||
ru: "ru",
|
||||
ja: "ja",
|
||||
ko: "ko",
|
||||
fr: "fr",
|
||||
de: "de",
|
||||
es: "es",
|
||||
pt: "pt",
|
||||
it: "it",
|
||||
};
|
||||
|
||||
export function getFormattedLanguage(lang: string): string {
|
||||
return languageTranslatorMap[lang.toLowerCase()] || lang;
|
||||
}
|
||||
|
||||
const defaultMiddleLang = "en";
|
||||
|
||||
type translationResponse = {
|
||||
responseData: {
|
||||
translatedText: string;
|
||||
};
|
||||
};
|
||||
|
||||
async function translate(
|
||||
text: string,
|
||||
from: string,
|
||||
to: string,
|
||||
): Promise<string> {
|
||||
if (from === to || text.length === 0) return text;
|
||||
const resp = await fetch(
|
||||
`https://api.mymemory.translated.net/get?q=${encodeURIComponent(
|
||||
text,
|
||||
)}&langpair=${from}|${to}`,
|
||||
);
|
||||
const data: translationResponse = await resp.json();
|
||||
return data.responseData.translatedText;
|
||||
}
|
||||
|
||||
export function doTranslate(
|
||||
content: string,
|
||||
from: string,
|
||||
to: string,
|
||||
): Promise<string> {
|
||||
from = getFormattedLanguage(from);
|
||||
to = getFormattedLanguage(to);
|
||||
|
||||
return translate(content, from, to);
|
||||
}
|
@ -1,41 +1,13 @@
|
||||
import { Plugin } from "vite";
|
||||
import path from "path";
|
||||
import * as fs from "fs";
|
||||
|
||||
const defaultDevLang = "cn";
|
||||
|
||||
function readJSON(...paths: string[]): any {
|
||||
return JSON.parse(fs.readFileSync(path.resolve(...paths)).toString());
|
||||
}
|
||||
import { Plugin, ResolvedConfig } from "vite";
|
||||
import { processTranslation } from "./translator";
|
||||
|
||||
export function createTranslationPlugin(): Plugin {
|
||||
return {
|
||||
name: "translate-plugin",
|
||||
apply: "build",
|
||||
configResolved(config) {
|
||||
async configResolved(config: ResolvedConfig) {
|
||||
try {
|
||||
const source = path.resolve(config.root, "src/resources/i18n");
|
||||
const files = fs.readdirSync(source);
|
||||
|
||||
const motherboard = `${defaultDevLang}.json`;
|
||||
|
||||
console.log(files.includes(`${defaultDevLang}.json`))
|
||||
if (files.length === 0) {
|
||||
console.warn("no translation files found");
|
||||
return;
|
||||
} else if (!files.includes(motherboard)) {
|
||||
console.warn(`no default translation file found (${defaultDevLang}.json)`);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = readJSON(source, motherboard);
|
||||
|
||||
files.forEach((file) => {
|
||||
if (file === motherboard) return;
|
||||
const lang = file.split(".")[0];
|
||||
const translation = readJSON(source, file);
|
||||
console.log(`translation file ${file} loaded`);
|
||||
});
|
||||
await processTranslation(config);
|
||||
} catch (e) {
|
||||
console.warn(`error during translation: ${e}`);
|
||||
}
|
||||
|
59
app/src/translator/io.ts
Normal file
59
app/src/translator/io.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
export function readJSON(...paths: string[]): any {
|
||||
return JSON.parse(fs.readFileSync(path.resolve(...paths)).toString());
|
||||
}
|
||||
|
||||
export function writeJSON(data: any, ...paths: string[]): void {
|
||||
fs.writeFileSync(path.resolve(...paths), JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
export function getMigration(
|
||||
mother: Record<string, any>,
|
||||
data: Record<string, any>,
|
||||
prefix: string,
|
||||
): string[] {
|
||||
return Object.keys(mother)
|
||||
.map((key): string[] => {
|
||||
const template = mother[key],
|
||||
translation = data[key];
|
||||
const val = [prefix.length === 0 ? key : `${prefix}.${key}`];
|
||||
|
||||
switch (typeof template) {
|
||||
case "string":
|
||||
if (typeof translation !== "string") return val;
|
||||
break;
|
||||
case "object":
|
||||
return getMigration(template, translation, val[0]);
|
||||
default:
|
||||
return typeof translation === typeof template ? [] : val;
|
||||
}
|
||||
})
|
||||
.flat()
|
||||
.filter((key) => key !== undefined && key.length > 0);
|
||||
}
|
||||
|
||||
export function getTranslation(data: Record<string, any>, path: string): any {
|
||||
const keys = path.split(".");
|
||||
let current = data;
|
||||
for (const key of keys) {
|
||||
if (current[key] === undefined) return undefined;
|
||||
current = current[key];
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
export function setTranslation(
|
||||
data: Record<string, any>,
|
||||
path: string,
|
||||
value: any,
|
||||
): void {
|
||||
const keys = path.split(".");
|
||||
let current = data;
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
if (current[keys[i]] === undefined) current[keys[i]] = {};
|
||||
current = current[keys[i]];
|
||||
}
|
||||
current[keys[keys.length - 1]] = value;
|
||||
}
|
60
app/src/translator/translator.ts
Normal file
60
app/src/translator/translator.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { ResolvedConfig } from "vite";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import {
|
||||
getMigration,
|
||||
getTranslation,
|
||||
readJSON,
|
||||
setTranslation,
|
||||
writeJSON,
|
||||
} from "./io";
|
||||
import { doTranslate } from "./adapter";
|
||||
|
||||
export const defaultDevLang = "cn";
|
||||
|
||||
export async function processTranslation(
|
||||
config: ResolvedConfig,
|
||||
): Promise<void> {
|
||||
const source = path.resolve(config.root, "src/resources/i18n");
|
||||
const files = fs.readdirSync(source);
|
||||
|
||||
const motherboard = `${defaultDevLang}.json`;
|
||||
|
||||
if (files.length === 0) {
|
||||
console.warn("no translation files found");
|
||||
return;
|
||||
} else if (!files.includes(motherboard)) {
|
||||
console.warn(`no default translation file found (${defaultDevLang}.json)`);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = readJSON(source, motherboard);
|
||||
|
||||
const target = files.filter((file) => file !== motherboard);
|
||||
for (const file of target) {
|
||||
const lang = file.split(".")[0];
|
||||
const translation = { ...readJSON(source, file) };
|
||||
|
||||
const migration = getMigration(data, translation, "");
|
||||
for (const key of migration) {
|
||||
const from = getTranslation(data, key);
|
||||
const to =
|
||||
typeof from === "string"
|
||||
? await doTranslate(from, defaultDevLang, lang)
|
||||
: from;
|
||||
|
||||
console.log(
|
||||
`[i18n] successfully translated: ${from} -> ${to} (lang: ${defaultDevLang} -> ${lang})`,
|
||||
);
|
||||
setTranslation(translation, key, to);
|
||||
}
|
||||
|
||||
if (migration.length > 0) {
|
||||
writeJSON(translation, source, file);
|
||||
}
|
||||
|
||||
console.info(
|
||||
`translation file ${file} loaded, ${migration.length} migration(s) found.`,
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user