Add DeepL translations as placeholders

This commit is contained in:
Imran Remtulla
2024-02-01 20:34:32 -05:00
parent 7d08e5225c
commit f50e791221
17 changed files with 150 additions and 62 deletions

View File

@@ -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?"

View File

@@ -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?"

View File

@@ -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",

View File

@@ -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?"

View File

@@ -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 ?"

View File

@@ -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?"

View File

@@ -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?"

View File

@@ -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": "アプリを削除しますか?"

View File

@@ -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?"

View File

@@ -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?",

View File

@@ -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?"

View File

@@ -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": "Удалить приложения?"

View File

@@ -1,8 +1,57 @@
// 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 deeplAPIKey = process.argv[2]
const neverAutoTranslate = {
steamMobile: ['*'],
steamChat: ['*'],
root: ['*'],
obtainiumExportHyphenatedLowercase: ['*'],
theme: ['de'],
appId: ['de']
}
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()
})
}
const main = async () => {
const translationsDir = __dirname
const templateFile = `${translationsDir}/en.json`
const otherFiles = fs.readdirSync(translationsDir).map(f => {
@@ -11,6 +60,7 @@ const otherFiles = fs.readdirSync(translationsDir).map(f => {
const templateTranslation = require(templateFile)
otherFiles.forEach(file => {
const thisTranslationOriginal = require(file)
const thisTranslationNew = {}
@@ -18,13 +68,52 @@ otherFiles.forEach(file => {
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])}`)
}
})
});
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)

View File

@@ -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?"

View File

@@ -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": "\"{}\" ı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?"

View File

@@ -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": "是否删除应用?"

View File

@@ -1038,7 +1038,7 @@ class AppsPageState extends State<AppsPage> {
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
: () {