From f50e7912217f53844d1e5b50d9a39681d783a30a Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Thu, 1 Feb 2024 20:34:32 -0500 Subject: [PATCH] Add DeepL translations as placeholders --- assets/translations/cs.json | 8 +- assets/translations/de.json | 2 +- assets/translations/en.json | 1 - assets/translations/es.json | 6 +- assets/translations/fr.json | 4 +- assets/translations/hu.json | 4 +- assets/translations/it.json | 2 +- assets/translations/ja.json | 6 +- assets/translations/nl.json | 6 +- assets/translations/pl.json | 6 +- assets/translations/pt.json | 4 +- assets/translations/ru.json | 6 +- assets/translations/standardize.js | 133 ++++++++++++++++++++++++----- assets/translations/sv.json | 10 +-- assets/translations/tr.json | 6 +- assets/translations/zh.json | 6 +- lib/pages/apps.dart | 2 +- 17 files changed, 150 insertions(+), 62 deletions(-) diff --git a/assets/translations/cs.json b/assets/translations/cs.json index 16bc773..1f1003b 100644 --- a/assets/translations/cs.json +++ b/assets/translations/cs.json @@ -292,15 +292,15 @@ "useLatestAssetDateAsReleaseDate": "Použít poslední nahrané dílo jako datum vydání", "defaultPseudoVersioningMethod": "Výchozí metoda pseudoverze", "partialAPKHash": "Částečný hash APK", - "APKLinkHash": "APK Link Hash", + "APKLinkHash": "Odkaz APK Hash", "directAPKLink": "Přímý odkaz APK", "pseudoVersionInUse": "Pseudoverze se používá", "installed": "Instalováno", "latest": "Nejnovější", "invertRegEx": "Invertovat regulární výraz", - "note": "Note", - "selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", - "badDownload": "The APK could not be parsed (incompatible or partial download)", + "note": "Poznámka", + "selfHostedNote": "Rozbalovací seznam \"{}\" lze použít k dosažení vlastních/obvyklých instancí libovolného zdroje.", + "badDownload": "APK nelze analyzovat (nekompatibilní nebo částečné stažení)", "removeAppQuestion": { "one": "Odstranit Apku?", "other": "Odstranit Apky?" diff --git a/assets/translations/de.json b/assets/translations/de.json index a5fe141..2bf8a03 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -300,7 +300,7 @@ "invertRegEx": "Regulären Ausdruck invertieren", "note": "Hinweis", "selfHostedNote": "Das „{}“-Dropdown-Menü kann verwendet werden, um selbst gehostete/angepasste Instanzen einer beliebigen Quelle zu erreichen.", - "badDownload": "The APK could not be parsed (incompatible or partial download)", + "badDownload": "Die APK konnte nicht geparst werden (inkompatibler oder teilweiser Download)", "removeAppQuestion": { "one": "App entfernen?", "other": "Apps entfernen?" diff --git a/assets/translations/en.json b/assets/translations/en.json index cecb9c4..fbc3b5f 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -80,7 +80,6 @@ "removeOutdatedFilter": "Remove Out-of-Date App Filter", "showOutdatedOnly": "Show Out-of-Date Apps Only", "filter": "Filter", - "filterActive": "Filter *", "filterApps": "Filter Apps", "appName": "App Name", "author": "Author", diff --git a/assets/translations/es.json b/assets/translations/es.json index 3af0908..a12abc4 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -298,9 +298,9 @@ "installed": "Instalado", "latest": "Versión más reciente", "invertRegEx": "Invertir expresión regular", - "note": "Note", - "selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", - "badDownload": "The APK could not be parsed (incompatible or partial download)", + "note": "Nota", + "selfHostedNote": "El desplegable \"{}\" puede utilizarse para acceder a instancias autoalojadas/personalizadas de cualquier fuente.", + "badDownload": "No se ha podido analizar el APK (incompatible o descarga parcial)", "removeAppQuestion": { "one": "¿Eliminar Aplicación?", "other": "¿Eliminar Aplicaciones?" diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 0b6e94d..06ff757 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -299,8 +299,8 @@ "latest": "Dernier", "invertRegEx": "Inverser l'expression régulière", "note": "Note", - "selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", - "badDownload": "The APK could not be parsed (incompatible or partial download)", + "selfHostedNote": "La liste déroulante \"{}\" peut être utilisée pour accéder aux instances auto-hébergées/personnalisées de n'importe quelle source.", + "badDownload": "L'APK n'a pas pu être analysé (téléchargement incompatible ou partiel)", "removeAppQuestion": { "one": "Supprimer l'application ?", "other": "Supprimer les applications ?" diff --git a/assets/translations/hu.json b/assets/translations/hu.json index 1d9ae8a..74b3713 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -298,9 +298,9 @@ "installed": "Telepített", "latest": "Legújabb", "invertRegEx": "Invertált reguláris kifejezés", - "note": "Note", + "note": "Megjegyzés:", "selfHostedNote": "A \"{}\" legördülő menü használható bármely forrás saját üzemeltetésű/egyéni példányainak eléréséhez.", - "badDownload": "The APK could not be parsed (incompatible or partial download)", + "badDownload": "Az APK-t nem lehetett elemezni (inkompatibilis vagy részleges letöltés)", "removeAppQuestion": { "one": "Eltávolítja az alkalmazást?", "other": "Eltávolítja az alkalmazást?" diff --git a/assets/translations/it.json b/assets/translations/it.json index d247391..d0d62e9 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -300,7 +300,7 @@ "invertRegEx": "Inverti espressione regolare", "note": "Nota", "selfHostedNote": "Il menu a tendina \"{}\" può essere usato per raggiungere istanze autogestite/personali di qualsiasi fonte.", - "badDownload": "The APK could not be parsed (incompatible or partial download)", + "badDownload": "Non è stato possibile analizzare l'APK (download incompatibile o parziale).", "removeAppQuestion": { "one": "Rimuovere l'app?", "other": "Rimuovere le app?" diff --git a/assets/translations/ja.json b/assets/translations/ja.json index dae398f..352dfad 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -298,9 +298,9 @@ "installed": "インストール済み", "latest": "最新", "invertRegEx": "正規表現を反転", - "note": "Note", - "selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", - "badDownload": "The APK could not be parsed (incompatible or partial download)", + "note": "注", + "selfHostedNote": "ドロップダウン\"{}\"を使用すると、あらゆるソースのセルフホスト/カスタムインスタンスにアクセスできます。", + "badDownload": "APK を解析できませんでした(互換性がないか、部分的にダウンロードされています)。", "removeAppQuestion": { "one": "アプリを削除しますか?", "other": "アプリを削除しますか?" diff --git a/assets/translations/nl.json b/assets/translations/nl.json index 9d53109..b9d73af 100644 --- a/assets/translations/nl.json +++ b/assets/translations/nl.json @@ -298,9 +298,9 @@ "installed": "Geïnstalleerd", "latest": "Laatste", "invertRegEx": "Reguliere expressie omkeren", - "note": "Note", - "selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", - "badDownload": "The APK could not be parsed (incompatible or partial download)", + "note": "Opmerking", + "selfHostedNote": "De \"{}\" dropdown kan gebruikt worden om zelf gehoste/aangepaste instanties van elke bron te bereiken.", + "badDownload": "De APK kon niet worden verwerkt (incompatibele of gedeeltelijke download)", "removeAppQuestion": { "one": "App verwijderen?", "other": "Apps verwijderen?" diff --git a/assets/translations/pl.json b/assets/translations/pl.json index 5620552..b8e1d60 100644 --- a/assets/translations/pl.json +++ b/assets/translations/pl.json @@ -298,9 +298,9 @@ "installed": "Zainstalowano", "latest": "Najnowszy", "invertRegEx": "Odwróć wyrażenie regularne", - "note": "Note", - "selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", - "badDownload": "The APK could not be parsed (incompatible or partial download)", + "note": "Uwaga", + "selfHostedNote": "Lista rozwijana \"{}\" może być używana do uzyskiwania dostępu do samodzielnie hostowanych / niestandardowych instancji dowolnego źródła.", + "badDownload": "Nie można przeanalizować pliku APK (niekompatybilny lub częściowo pobrany).", "removeAppQuestion": { "one": "Usunąć aplikację?", "few": "Usunąć aplikacje?", diff --git a/assets/translations/pt.json b/assets/translations/pt.json index 53bd69e..d264842 100644 --- a/assets/translations/pt.json +++ b/assets/translations/pt.json @@ -298,9 +298,9 @@ "installed": "Instalado", "latest": "Mais recente", "invertRegEx": "Inverter expressão regular", - "note": "Note", + "note": "Nota", "selfHostedNote": "O menu suspenso \"{}\" pode ser usado para acessar instâncias auto-hospedadas/personalizadas de qualquer fonte.", - "badDownload": "The APK could not be parsed (incompatible or partial download)", + "badDownload": "Não foi possível analisar o APK (transferência incompatível ou parcial)", "removeAppQuestion": { "one": "Remover aplicativo?", "other": "Remover aplicativos?" diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 019707b..654d484 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -298,9 +298,9 @@ "installed": "Установлен", "latest": "Последний", "invertRegEx": "Инвертировать регулярное выражение", - "note": "Note", - "selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", - "badDownload": "The APK could not be parsed (incompatible or partial download)", + "note": "Примечание", + "selfHostedNote": "Выпадающий список \"{}\" можно использовать для доступа к самостоятельно размещенным/настроенным экземплярам любого источника.", + "badDownload": "APK не удалось разобрать (несовместимая или неполная загрузка)", "removeAppQuestion": { "one": "Удалить приложение?", "other": "Удалить приложения?" diff --git a/assets/translations/standardize.js b/assets/translations/standardize.js index 3fd64ed..a1188e3 100644 --- a/assets/translations/standardize.js +++ b/assets/translations/standardize.js @@ -1,30 +1,119 @@ // Take one (hardcoded) translation file and ensure that all other translation files have the same keys in the same order -// Then report which other translation files have identical items +// Then report which other translation files have identical items (or auto-translate them if a DeepL API key is provided) const fs = require('fs') +const https = require('https') -const translationsDir = __dirname -const templateFile = `${translationsDir}/en.json` -const otherFiles = fs.readdirSync(translationsDir).map(f => { - return `${translationsDir}/${f}` -}).filter(f => f.endsWith('.json') && f != templateFile) +const deeplAPIKey = process.argv[2] -const templateTranslation = require(templateFile) +const neverAutoTranslate = { + steamMobile: ['*'], + steamChat: ['*'], + root: ['*'], + obtainiumExportHyphenatedLowercase: ['*'], + theme: ['de'], + appId: ['de'] +} -otherFiles.forEach(file => { - const thisTranslationOriginal = require(file) - const thisTranslationNew = {} - Object.keys(templateTranslation).forEach(k => { - thisTranslationNew[k] = thisTranslationOriginal[k] || templateTranslation[k] - }) - fs.writeFileSync(file, `${JSON.stringify(thisTranslationNew, null, ' ')}\n`) -}); - -otherFiles.forEach(file => { - const thisTranslation = require(file) - Object.keys(templateTranslation).forEach(k => { - if (JSON.stringify(thisTranslation[k]) == JSON.stringify(templateTranslation[k])) { - console.log(`${file} :::: ${k} :::: ${JSON.stringify(thisTranslation[k])}`) +const translateText = async (text, targetLang, authKey) => { + return new Promise((resolve, reject) => { + const postData = `text=${encodeURIComponent(text)}&target_lang=${encodeURIComponent(targetLang)}&source_lang=EN` + const options = { + hostname: 'api-free.deepl.com', + port: 443, + path: '/v2/translate', + method: 'POST', + headers: { + 'Authorization': `DeepL-Auth-Key ${authKey}`, + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': Buffer.byteLength(postData) + } } + const req = https.request(options, (res) => { + let responseData = '' + res.on('data', (chunk) => { + responseData += chunk + }) + res.on('end', () => { + try { + const jsonResponse = JSON.parse(responseData) + resolve(jsonResponse) + } catch (error) { + reject(error) + } + }) + }) + req.on('error', (error) => { + reject(error) + }) + req.write(postData) + req.end() }) -}); \ No newline at end of file +} + +const main = async () => { + const translationsDir = __dirname + const templateFile = `${translationsDir}/en.json` + const otherFiles = fs.readdirSync(translationsDir).map(f => { + return `${translationsDir}/${f}` + }).filter(f => f.endsWith('.json') && f != templateFile) + + const templateTranslation = require(templateFile) + + + otherFiles.forEach(file => { + const thisTranslationOriginal = require(file) + const thisTranslationNew = {} + Object.keys(templateTranslation).forEach(k => { + thisTranslationNew[k] = thisTranslationOriginal[k] || templateTranslation[k] + }) + fs.writeFileSync(file, `${JSON.stringify(thisTranslationNew, null, ' ')}\n`) + }) + + for (let i in otherFiles) { + const file = otherFiles[i] + const thisTranslation = require(file) + const translationKeys = Object.keys(templateTranslation) + for (let j in translationKeys) { + const k = translationKeys[j] + if (JSON.stringify(thisTranslation[k]) == JSON.stringify(templateTranslation[k])) { + const lang = file.split('/').pop().split('.')[0] + if (!neverAutoTranslate[k] || (neverAutoTranslate[k].indexOf('*') < 0 && neverAutoTranslate[k].indexOf(lang) < 0)) { + const reportLine = `${file} :::: ${k} :::: ${JSON.stringify(thisTranslation[k])}` + if (deeplAPIKey) { + const translateFunc = async (str) => { + const response = await translateText(str, lang, deeplAPIKey) + if (response.translations) { + return response.translations[0].text + } else { + throw JSON.stringify(response) + } + } + try { + if (typeof templateTranslation[k] == 'string') { + thisTranslation[k] = await translateFunc(thisTranslation[k]) + } else { + const subKeys = Object.keys(templateTranslation[k]) + for (let n in subKeys) { + const kk = subKeys[n] + thisTranslation[k][kk] = await translateFunc(thisTranslation[k][kk]) + } + } + } catch (e) { + if (typeof e == 'string') { + console.log(`${reportLine} :::: ${e}`) + } else { + throw e + } + } + } else { + console.log(reportLine) + } + } + } + } + fs.writeFileSync(file, `${JSON.stringify(thisTranslation, null, ' ')}\n`) + } +} + +main().catch(e => console.error) diff --git a/assets/translations/sv.json b/assets/translations/sv.json index 18c806f..966580a 100644 --- a/assets/translations/sv.json +++ b/assets/translations/sv.json @@ -288,19 +288,19 @@ "useSystemFont": "Använd systemteckensnittet", "systemFontError": "Fel vid laddning av systemteckensnittet: {}", "useVersionCodeAsOSVersion": "Använd appversionskoden som OS-upptäckt version", - "requestHeader": "Request header", + "requestHeader": "Rubrik för begäran", "useLatestAssetDateAsReleaseDate": "Använd senaste tillgångsuppladdning som releasedatum", "defaultPseudoVersioningMethod": "Standard pseudoversionsmetod", "partialAPKHash": "Delvis APK-hash", - "APKLinkHash": "APK Link Hash", + "APKLinkHash": "APK-länk Hash", "directAPKLink": "Direkt APK-länk", "pseudoVersionInUse": "En pseudoversion används", "installed": "Installerad", "latest": "Senast", "invertRegEx": "Invertera reguljärt uttryck", - "note": "Note", - "selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", - "badDownload": "The APK could not be parsed (incompatible or partial download)", + "note": "Anmärkning", + "selfHostedNote": "Rullgardinsmenyn \"{}\" kan användas för att nå självhostade/anpassade instanser av valfri källa.", + "badDownload": "APK kunde inte analyseras (inkompatibel eller partiell nedladdning)", "removeAppQuestion": { "one": "Ta Bort App?", "other": "Ta Bort Appar?" diff --git a/assets/translations/tr.json b/assets/translations/tr.json index e374d00..6584b25 100644 --- a/assets/translations/tr.json +++ b/assets/translations/tr.json @@ -298,9 +298,9 @@ "installed": "Kurulmuş", "latest": "En sonuncu", "invertRegEx": "Normal ifadeyi ters çevir", - "note": "Note", - "selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", - "badDownload": "The APK could not be parsed (incompatible or partial download)", + "note": "Not", + "selfHostedNote": "\"{}\" açılır menüsü, herhangi bir kaynağın kendi kendine barındırılan/özel örneklerine ulaşmak için kullanılabilir.", + "badDownload": "APK ayrıştırılamadı (uyumsuz veya kısmi indirme)", "removeAppQuestion": { "one": "Uygulamayı Kaldır?", "other": "Uygulamaları Kaldır?" diff --git a/assets/translations/zh.json b/assets/translations/zh.json index b00e819..87f0bde 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -298,9 +298,9 @@ "installed": "已安装", "latest": "最新的", "invertRegEx": "反转正则表达式", - "note": "Note", - "selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", - "badDownload": "The APK could not be parsed (incompatible or partial download)", + "note": "备注", + "selfHostedNote": "{}\"下拉菜单可用于访问任何来源的自托管/自定义实例。", + "badDownload": "无法解析 APK(不兼容或部分下载)", "removeAppQuestion": { "one": "是否删除应用?", "other": "是否删除应用?" diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index f4b6b84..0a94dd6 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -1038,7 +1038,7 @@ class AppsPageState extends State { IconButton( color: Theme.of(context).colorScheme.primary, style: const ButtonStyle(visualDensity: VisualDensity.compact), - tooltip: isFilterOff ? tr('filter') : tr('filterActive'), + tooltip: '${tr('filter')}${isFilterOff ? '' : ' *'}', onPressed: isFilterOff ? showFilterDialog : () {