Skip to content

Commit

Permalink
Merge pull request #503 from Kodylow/translate-by-key
Browse files Browse the repository at this point in the history
fix: translate by key + change to beta software warning
  • Loading branch information
elsirion committed Aug 23, 2024
2 parents 53b73a0 + 5abf2f4 commit 74379ab
Show file tree
Hide file tree
Showing 14 changed files with 173 additions and 64 deletions.
4 changes: 4 additions & 0 deletions JUSTFILE
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ guardian:
yarn nix-guardian
reset dc:
cp original-docker-compose.yml docker-compose.yml && docker compose down -v && echo 'Removing fm dirs' && sudo rm -rf fm_* && echo 'Done'
translate:
node scripts/translate.js
translate-key ui key:
node scripts/translate.js {{ui}} {{key}}
4 changes: 2 additions & 2 deletions apps/guardian-ui/src/languages/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@
"submit": "Enviar"
},
"role-selector": {
"disclaimer-title": "Fedimint és un programari alfa",
"disclaimer-text": "Estàs utilitzant una versió molt primerenca de Fedimint. Si us plau, utilitza-ho amb precaució i informa dels problemes als desenvolupadors de Fedimint a Github.",
"disclaimer-title": "Fedimint és un programari beta.",
"disclaimer-text": "Si us plau, informa de qualsevol problema a https://github.com/fedimint/fedimint/issues",
"leader": {
"label": "Configura el líder",
"description": "Introdueix la configuració que altres Guardians aproven. Un Guardian actua com a Líder de Configuració."
Expand Down
4 changes: 2 additions & 2 deletions apps/guardian-ui/src/languages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@
"submit": "Einreichen"
},
"role-selector": {
"disclaimer-title": "Fedimint ist Alpha-Software",
"disclaimer-text": "Sie verwenden eine sehr frühe Version von Fedimint. Bitte verwenden Sie sie mit Vorsicht und melden Sie Probleme den Fedimint-Entwicklern auf Github.",
"disclaimer-title": "Fedimint ist Beta-Software",
"disclaimer-text": "Bitte melde Probleme unter https://github.com/fedimint/fedimint/issues",
"leader": {
"label": "Einrichtungsleiter",
"description": "Geben Sie Konfigurationseinstellungen ein, die von anderen Hütern genehmigt werden. Ein Hüter fungiert als Setup-Leiter."
Expand Down
4 changes: 2 additions & 2 deletions apps/guardian-ui/src/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@
"submit": "Submit"
},
"role-selector": {
"disclaimer-title": "Fedimint is alpha software",
"disclaimer-text": "You are using a very early version of Fedimint. Please use with caution & report issues to the Fedimint developers on Github.",
"disclaimer-title": "Fedimint is beta software",
"disclaimer-text": "Please report issues at https://github.com/fedimint/fedimint/issues",
"leader": {
"label": "Setup Leader",
"description": "Enter configuration settings that other Guardians approve. One Guardian acts as Setup Leader. "
Expand Down
4 changes: 2 additions & 2 deletions apps/guardian-ui/src/languages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@
"submit": "Enviar"
},
"role-selector": {
"disclaimer-title": "Fedimint es software en alfa.",
"disclaimer-text": "Estás utilizando una versión muy temprana de Fedimint. Por favor, úsala con precaución e informa sobre problemas a los desarrolladores de Fedimint en Github.",
"disclaimer-title": "Fedimint es un software beta.",
"disclaimer-text": "Por favor, informe de los problemas en https://github.com/fedimint/fedimint/issues",
"leader": {
"label": "Configuración de líder",
"description": "Ingrese la configuración que otros Guardianes aprueban. Un Guardián actúa como Líder de Configuración."
Expand Down
4 changes: 2 additions & 2 deletions apps/guardian-ui/src/languages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@
"submit": "Soumettre"
},
"role-selector": {
"disclaimer-title": "Fedimint est un logiciel alpha.",
"disclaimer-text": "Vous utilisez une version très précoce de Fedimint. Veuillez l'utiliser avec prudence et signaler les problèmes aux développeurs de Fedimint sur Github.",
"disclaimer-title": "Fedimint est un logiciel bêta",
"disclaimer-text": "Veuillez signaler les problèmes à https://github.com/fedimint/fedimint/issues",
"leader": {
"label": "Leader de configuration",
"description": "Entrez les paramètres de configuration que d'autres Gardiens approuvent. Un Gardien agit en tant que Leader de Configuration."
Expand Down
4 changes: 2 additions & 2 deletions apps/guardian-ui/src/languages/hu.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@
"submit": "Benyújtás"
},
"role-selector": {
"disclaimer-title": "A Fedimint alfa szoftver.",
"disclaimer-text": "Nagyon korai verzióját használja a Fedimintnak. Kérjük, használja óvatosan, és jelentsen problémákat a Fedimint fejlesztőinek a Githubon.",
"disclaimer-title": "A Fedimint béta szoftver.",
"disclaimer-text": "Kérjük, jelezze a problémákat a https://github.com/fedimint/fedimint/issues oldalon.",
"leader": {
"label": "Beállító vezető",
"description": "Írja be a konfigurációs beállításokat, amelyeket más Őrzők jóváhagynak. Egy Őrző a Beállítási Vezető."
Expand Down
4 changes: 2 additions & 2 deletions apps/guardian-ui/src/languages/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@
"submit": "Enviar"
},
"role-selector": {
"disclaimer-title": "Fedimint es software en alfa.",
"disclaimer-text": "Estás utilizando una versión muy temprana de Fedimint. Por favor, úsala con precaución e informa sobre problemas a los desarrolladores de Fedimint en Github.",
"disclaimer-title": "Fedimint è un software beta",
"disclaimer-text": "Segnala problemi su https://github.com/fedimint/fedimint/issues",
"leader": {
"label": "Configurador de Líder",
"description": "Ingrese la configuración que otros Guardianes aprueban. Un Guardián actúa como Líder de Configuración."
Expand Down
4 changes: 2 additions & 2 deletions apps/guardian-ui/src/languages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@
"submit": "提出する"
},
"role-selector": {
"disclaimer-title": "Fedimintはアルファソフトウェアです。",
"disclaimer-text": "非常に初期のバージョンのFedimintを使用しています。注意して使用し、問題はGithubのFedimint開発者に報告してください",
"disclaimer-title": "Fedimintはベータ版ソフトウェアです",
"disclaimer-text": "問題はhttps://github.com/fedimint/fedimint/issuesで報告してください",
"leader": {
"label": "セットアップリーダー",
"description": "他のガーディアンが承認する設定を入力してください。1人のガーディアンがセットアップリーダーとして行動します。"
Expand Down
4 changes: 2 additions & 2 deletions apps/guardian-ui/src/languages/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,8 @@
"title": "돌아온 것을 환영합니다!"
},
"role-selector": {
"disclaimer-text": "귀하는 매우 초기 버전의 Fedimint를 사용하고 있습니다. 주의해서 사용하고 Github의 Fedimint 개발자에게 문제를 보고하세요.",
"disclaimer-title": "Fedimint는 알파 소프트웨어입니다.",
"disclaimer-text": "문제점은 https://github.com/fedimint/fedimint/issues 에 보고해 주세요.",
"disclaimer-title": "Fedimint는 베타 소프트웨어입니다",
"follower": {
"description": "구성 리더가 구성을 설정하면 다른 보호자가 이 옵션을 선택하여 설정을 승인하고 연합을 만듭니다.",
"label": "팔로워"
Expand Down
4 changes: 2 additions & 2 deletions apps/guardian-ui/src/languages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@
"submit": "Enviar"
},
"role-selector": {
"disclaimer-title": "Fedimint é um software em versão alfa.",
"disclaimer-text": "Você está usando uma versão muito antiga do Fedimint. Por favor, use com cautela e reporte problemas aos desenvolvedores do Fedimint no Github.",
"disclaimer-title": "Fedimint é um software beta.",
"disclaimer-text": "Por favor, reporte problemas em https://github.com/fedimint/fedimint/issues",
"leader": {
"label": "Configuração do Líder",
"description": "Insira as configurações que outros Guardiões aprovam. Um Guardião atua como Líder de Configuração."
Expand Down
4 changes: 2 additions & 2 deletions apps/guardian-ui/src/languages/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@
"submit": "Отправить"
},
"role-selector": {
"disclaimer-title": "Fedimint это альфа-программное обеспечение",
"disclaimer-text": "Вы используете очень раннюю версию Fedimint. Пожалуйста, используйте с осторожностью и сообщайте о проблемах разработчикам Fedimint на Github.",
"disclaimer-title": "Fedimint - это бета-версия программного обеспечения.",
"disclaimer-text": "Пожалуйста, сообщайте о проблемах по адресу https://github.com/fedimint/fedimint/issues",
"leader": {
"label": "Настройка лидера",
"description": "Введите настройки конфигурации, которые одобряют другие Стражи. Один Страж выступает в роли Руководителя настройки."
Expand Down
4 changes: 2 additions & 2 deletions apps/guardian-ui/src/languages/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@
"submit": "提交"
},
"role-selector": {
"disclaimer-title": "Fedimint 是测试版软件",
"disclaimer-text": "您正在使用非常早期的 Fedimint 版本。请谨慎使用,并将问题报告给 GitHub 上的 Fedimint 开发者。",
"disclaimer-title": "Fedimint是测试版软件",
"disclaimer-text": "请在https://github.com/fedimint/fedimint/issues报告问题",
"leader": {
"label": "设置领导者",
"description": "输入其他守护者批准的配置设置。一个守护者担任设置领导。"
Expand Down
185 changes: 145 additions & 40 deletions scripts/translate.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
const fs = require('fs/promises');
const path = require('path');
const { execSync } = require('child_process');
const OpenAI = require('openai');

const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});

const srcPaths = [
'apps/gateway-ui/src/languages',
'apps/guardian-ui/src/languages',
const languages = [
'ca',
'de',
'es',
'fr',
'hu',
'it',
'ja',
'ko',
'pt',
'ru',
'zh',
];

// Get languages from command line arguments, excluding the first two default arguments (node path and script path)
const languages = process.argv.slice(2)[0].split(' ');
let srcPaths = [];
const targetFile = process.argv[2];
switch (targetFile) {
case 'gateway':
srcPaths = ['apps/gateway-ui/src/languages'];
break;
case 'guardian':
srcPaths = ['apps/guardian-ui/src/languages'];
break;
default:
srcPaths = [
'apps/gateway-ui/src/languages',
'apps/guardian-ui/src/languages',
];
}
const targetKey = process.argv[3];

async function installOpenAI() {
console.log('Installing OpenAI package...');
Expand All @@ -29,7 +47,40 @@ async function uninstallOpenAI() {
});
}

async function translateAndFill() {
function flattenObject(obj, prefix = '') {
return Object.keys(obj).reduce((acc, k) => {
const pre = prefix.length ? prefix + '.' : '';
if (
typeof obj[k] === 'object' &&
obj[k] !== null &&
!Array.isArray(obj[k])
) {
Object.assign(acc, flattenObject(obj[k], pre + k));
} else {
acc[pre + k] = obj[k];
}
return acc;
}, {});
}

function unflattenObject(obj) {
const result = {};
for (const key in obj) {
const keys = key.split('.');
let current = result;
for (let i = 0; i < keys.length; i++) {
if (i === keys.length - 1) {
current[keys[i]] = obj[key];
} else {
current[keys[i]] = current[keys[i]] || {};
current = current[keys[i]];
}
}
}
return result;
}

async function translateAndFill(openaiInstance) {
try {
for (const srcPath of srcPaths) {
const srcFile = `${srcPath}/en.json`;
Expand All @@ -43,15 +94,45 @@ async function translateAndFill() {
} catch (error) {
console.log(`Creating new file for language: ${lang}`);
}
const updatedData = await fillMissingKeys(
srcData,
targetData,
lang,
openai
);

if (targetKey) {
const keys = targetKey.split('.');
const dataToTranslate = keys.reduce(
(obj, key) => obj && obj[key],
srcData
);

if (dataToTranslate === undefined) {
console.error(`Key not found in source: ${targetKey}`);
continue;
}

console.log(`Translating key: ${targetKey}`);
const translation = await translateWithOpenAI(
dataToTranslate,
lang,
openaiInstance
);

// Update the specific key in the target data
let current = targetData;
for (let i = 0; i < keys.length - 1; i++) {
if (!current[keys[i]]) current[keys[i]] = {};
current = current[keys[i]];
}
current[keys[keys.length - 1]] = translation;
} else {
targetData = await fillMissingKeys(
srcData,
targetData,
lang,
openaiInstance
);
}

await fs.writeFile(
targetFile,
JSON.stringify(updatedData, null, 2),
JSON.stringify(targetData, null, 2),
'utf8'
);
console.log(`Updated/created file for language: ${lang}`);
Expand All @@ -62,52 +143,72 @@ async function translateAndFill() {
}
}

async function fillMissingKeys(srcObj, targetObj, lang, path = '') {
async function fillMissingKeys(srcObj, targetObj, lang, openaiInstance) {
const updatedObj = { ...targetObj };
for (const key in srcObj) {
const newPath = path ? `${path}.${key}` : key;

if (typeof srcObj[key] === 'object' && srcObj[key] !== null) {
if (!updatedObj[key] || typeof updatedObj[key] !== 'object') {
updatedObj[key] = {};
if (isStringObject(srcObj[key])) {
const srcString = objectToString(srcObj[key]);
if (updatedObj[key] === undefined) {
console.log(`Translating key: ${key}`);
await retryWithExponentialBackoff(async () => {
const translation = await translateWithOpenAI(
srcString,
lang,
openaiInstance
);
updatedObj[key] = translation;
});
} else {
console.log(`Skipping key: ${key} (already exists)`);
}
} else if (typeof srcObj[key] === 'object' && srcObj[key] !== null) {
updatedObj[key] = await fillMissingKeys(
srcObj[key],
updatedObj[key],
updatedObj[key] || {},
lang,
newPath
openaiInstance
);
} else if (updatedObj[key] === undefined) {
console.log(`Translating and adding missing key: ${newPath}`);
console.log(`Translating key: ${key}`);
await retryWithExponentialBackoff(async () => {
const translation = await translateWithOpenAI(srcObj[key], lang);
const translation = await translateWithOpenAI(
srcObj[key],
lang,
openaiInstance
);
updatedObj[key] = translation;
});
} else {
console.log(`Skipping key: ${newPath}`);
console.log(`Skipping key: ${key} (already exists)`);
}
}

return updatedObj;
}

async function translateWithOpenAI(text, targetLang) {
function isStringObject(obj) {
return (
typeof obj === 'object' &&
obj !== null &&
Object.keys(obj).every((key) => !Number.isNaN(parseInt(key)))
);
}

function objectToString(obj) {
return Object.values(obj).join('');
}

async function translateWithOpenAI(text, targetLang, openaiInstance) {
const prompt = `Translate the following text to ${targetLang}. Return only the translated string, without any additional text or explanations:\n\n${text}`;

const response = await openai.chat.completions.create({
model: 'gpt-4o-mini',
const response = await openaiInstance.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: prompt }],
temperature: 0.3,
max_tokens: 150,
stream: false,
});

if (
response &&
response?.choices &&
response.choices.length > 0 &&
response.choices[0].message
) {
if (response?.choices?.[0]?.message?.content) {
return response.choices[0].message.content
.trim()
.replace(/^["']|["']$/g, '');
Expand Down Expand Up @@ -140,7 +241,11 @@ async function retryWithExponentialBackoff(
async function main() {
try {
await installOpenAI();
await translateAndFill();
const { default: OpenAI } = await import('openai');
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
await translateAndFill(openai);
} finally {
await uninstallOpenAI();
}
Expand Down

0 comments on commit 74379ab

Please sign in to comment.