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í", "useLatestAssetDateAsReleaseDate": "Použít poslední nahrané dílo jako datum vydání",
"defaultPseudoVersioningMethod": "Výchozí metoda pseudoverze", "defaultPseudoVersioningMethod": "Výchozí metoda pseudoverze",
"partialAPKHash": "Částečný hash APK", "partialAPKHash": "Částečný hash APK",
"APKLinkHash": "APK Link Hash", "APKLinkHash": "Odkaz APK Hash",
"directAPKLink": "Přímý odkaz APK", "directAPKLink": "Přímý odkaz APK",
"pseudoVersionInUse": "Pseudoverze se používá", "pseudoVersionInUse": "Pseudoverze se používá",
"installed": "Instalováno", "installed": "Instalováno",
"latest": "Nejnovější", "latest": "Nejnovější",
"invertRegEx": "Invertovat regulární výraz", "invertRegEx": "Invertovat regulární výraz",
"note": "Note", "note": "Poznámka",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", "selfHostedNote": "Rozbalovací seznam \"{}\" lze použít k dosažení vlastních/obvyklých instancí libovolného zdroje.",
"badDownload": "The APK could not be parsed (incompatible or partial download)", "badDownload": "APK nelze analyzovat (nekompatibilní nebo částečné stažení)",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Odstranit Apku?", "one": "Odstranit Apku?",
"other": "Odstranit Apky?" "other": "Odstranit Apky?"

View File

@@ -300,7 +300,7 @@
"invertRegEx": "Regulären Ausdruck invertieren", "invertRegEx": "Regulären Ausdruck invertieren",
"note": "Hinweis", "note": "Hinweis",
"selfHostedNote": "Das „{}“-Dropdown-Menü kann verwendet werden, um selbst gehostete/angepasste Instanzen einer beliebigen Quelle zu erreichen.", "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": { "removeAppQuestion": {
"one": "App entfernen?", "one": "App entfernen?",
"other": "Apps entfernen?" "other": "Apps entfernen?"

View File

@@ -80,7 +80,6 @@
"removeOutdatedFilter": "Remove Out-of-Date App Filter", "removeOutdatedFilter": "Remove Out-of-Date App Filter",
"showOutdatedOnly": "Show Out-of-Date Apps Only", "showOutdatedOnly": "Show Out-of-Date Apps Only",
"filter": "Filter", "filter": "Filter",
"filterActive": "Filter *",
"filterApps": "Filter Apps", "filterApps": "Filter Apps",
"appName": "App Name", "appName": "App Name",
"author": "Author", "author": "Author",

View File

@@ -298,9 +298,9 @@
"installed": "Instalado", "installed": "Instalado",
"latest": "Versión más reciente", "latest": "Versión más reciente",
"invertRegEx": "Invertir expresión regular", "invertRegEx": "Invertir expresión regular",
"note": "Note", "note": "Nota",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", "selfHostedNote": "El desplegable \"{}\" puede utilizarse para acceder a instancias autoalojadas/personalizadas de cualquier fuente.",
"badDownload": "The APK could not be parsed (incompatible or partial download)", "badDownload": "No se ha podido analizar el APK (incompatible o descarga parcial)",
"removeAppQuestion": { "removeAppQuestion": {
"one": "¿Eliminar Aplicación?", "one": "¿Eliminar Aplicación?",
"other": "¿Eliminar Aplicaciones?" "other": "¿Eliminar Aplicaciones?"

View File

@@ -299,8 +299,8 @@
"latest": "Dernier", "latest": "Dernier",
"invertRegEx": "Inverser l'expression régulière", "invertRegEx": "Inverser l'expression régulière",
"note": "Note", "note": "Note",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", "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": "The APK could not be parsed (incompatible or partial download)", "badDownload": "L'APK n'a pas pu être analysé (téléchargement incompatible ou partiel)",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Supprimer l'application ?", "one": "Supprimer l'application ?",
"other": "Supprimer les applications ?" "other": "Supprimer les applications ?"

View File

@@ -298,9 +298,9 @@
"installed": "Telepített", "installed": "Telepített",
"latest": "Legújabb", "latest": "Legújabb",
"invertRegEx": "Invertált reguláris kifejezés", "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.", "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": { "removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?", "one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazást?" "other": "Eltávolítja az alkalmazást?"

View File

@@ -300,7 +300,7 @@
"invertRegEx": "Inverti espressione regolare", "invertRegEx": "Inverti espressione regolare",
"note": "Nota", "note": "Nota",
"selfHostedNote": "Il menu a tendina \"{}\" può essere usato per raggiungere istanze autogestite/personali di qualsiasi fonte.", "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": { "removeAppQuestion": {
"one": "Rimuovere l'app?", "one": "Rimuovere l'app?",
"other": "Rimuovere le app?" "other": "Rimuovere le app?"

View File

@@ -298,9 +298,9 @@
"installed": "インストール済み", "installed": "インストール済み",
"latest": "最新", "latest": "最新",
"invertRegEx": "正規表現を反転", "invertRegEx": "正規表現を反転",
"note": "Note", "note": "",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", "selfHostedNote": "ドロップダウン\"{}\"を使用すると、あらゆるソースのセルフホスト/カスタムインスタンスにアクセスできます。",
"badDownload": "The APK could not be parsed (incompatible or partial download)", "badDownload": "APK を解析できませんでした(互換性がないか、部分的にダウンロードされています)。",
"removeAppQuestion": { "removeAppQuestion": {
"one": "アプリを削除しますか?", "one": "アプリを削除しますか?",
"other": "アプリを削除しますか?" "other": "アプリを削除しますか?"

View File

@@ -298,9 +298,9 @@
"installed": "Geïnstalleerd", "installed": "Geïnstalleerd",
"latest": "Laatste", "latest": "Laatste",
"invertRegEx": "Reguliere expressie omkeren", "invertRegEx": "Reguliere expressie omkeren",
"note": "Note", "note": "Opmerking",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", "selfHostedNote": "De \"{}\" dropdown kan gebruikt worden om zelf gehoste/aangepaste instanties van elke bron te bereiken.",
"badDownload": "The APK could not be parsed (incompatible or partial download)", "badDownload": "De APK kon niet worden verwerkt (incompatibele of gedeeltelijke download)",
"removeAppQuestion": { "removeAppQuestion": {
"one": "App verwijderen?", "one": "App verwijderen?",
"other": "Apps verwijderen?" "other": "Apps verwijderen?"

View File

@@ -298,9 +298,9 @@
"installed": "Zainstalowano", "installed": "Zainstalowano",
"latest": "Najnowszy", "latest": "Najnowszy",
"invertRegEx": "Odwróć wyrażenie regularne", "invertRegEx": "Odwróć wyrażenie regularne",
"note": "Note", "note": "Uwaga",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", "selfHostedNote": "Lista rozwijana \"{}\" może być używana do uzyskiwania dostępu do samodzielnie hostowanych / niestandardowych instancji dowolnego źródła.",
"badDownload": "The APK could not be parsed (incompatible or partial download)", "badDownload": "Nie można przeanalizować pliku APK (niekompatybilny lub częściowo pobrany).",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Usunąć aplikację?", "one": "Usunąć aplikację?",
"few": "Usunąć aplikacje?", "few": "Usunąć aplikacje?",

View File

@@ -298,9 +298,9 @@
"installed": "Instalado", "installed": "Instalado",
"latest": "Mais recente", "latest": "Mais recente",
"invertRegEx": "Inverter expressão regular", "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.", "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": { "removeAppQuestion": {
"one": "Remover aplicativo?", "one": "Remover aplicativo?",
"other": "Remover aplicativos?" "other": "Remover aplicativos?"

View File

@@ -298,9 +298,9 @@
"installed": "Установлен", "installed": "Установлен",
"latest": "Последний", "latest": "Последний",
"invertRegEx": "Инвертировать регулярное выражение", "invertRegEx": "Инвертировать регулярное выражение",
"note": "Note", "note": "Примечание",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", "selfHostedNote": "Выпадающий список \"{}\" можно использовать для доступа к самостоятельно размещенным/настроенным экземплярам любого источника.",
"badDownload": "The APK could not be parsed (incompatible or partial download)", "badDownload": "APK не удалось разобрать (несовместимая или неполная загрузка)",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Удалить приложение?", "one": "Удалить приложение?",
"other": "Удалить приложения?" "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 // 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 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 translationsDir = __dirname
const templateFile = `${translationsDir}/en.json` const templateFile = `${translationsDir}/en.json`
const otherFiles = fs.readdirSync(translationsDir).map(f => { const otherFiles = fs.readdirSync(translationsDir).map(f => {
@@ -11,6 +60,7 @@ const otherFiles = fs.readdirSync(translationsDir).map(f => {
const templateTranslation = require(templateFile) const templateTranslation = require(templateFile)
otherFiles.forEach(file => { otherFiles.forEach(file => {
const thisTranslationOriginal = require(file) const thisTranslationOriginal = require(file)
const thisTranslationNew = {} const thisTranslationNew = {}
@@ -18,13 +68,52 @@ otherFiles.forEach(file => {
thisTranslationNew[k] = thisTranslationOriginal[k] || templateTranslation[k] thisTranslationNew[k] = thisTranslationOriginal[k] || templateTranslation[k]
}) })
fs.writeFileSync(file, `${JSON.stringify(thisTranslationNew, null, ' ')}\n`) 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", "useSystemFont": "Använd systemteckensnittet",
"systemFontError": "Fel vid laddning av systemteckensnittet: {}", "systemFontError": "Fel vid laddning av systemteckensnittet: {}",
"useVersionCodeAsOSVersion": "Använd appversionskoden som OS-upptäckt version", "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", "useLatestAssetDateAsReleaseDate": "Använd senaste tillgångsuppladdning som releasedatum",
"defaultPseudoVersioningMethod": "Standard pseudoversionsmetod", "defaultPseudoVersioningMethod": "Standard pseudoversionsmetod",
"partialAPKHash": "Delvis APK-hash", "partialAPKHash": "Delvis APK-hash",
"APKLinkHash": "APK Link Hash", "APKLinkHash": "APK-länk Hash",
"directAPKLink": "Direkt APK-länk", "directAPKLink": "Direkt APK-länk",
"pseudoVersionInUse": "En pseudoversion används", "pseudoVersionInUse": "En pseudoversion används",
"installed": "Installerad", "installed": "Installerad",
"latest": "Senast", "latest": "Senast",
"invertRegEx": "Invertera reguljärt uttryck", "invertRegEx": "Invertera reguljärt uttryck",
"note": "Note", "note": "Anmärkning",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", "selfHostedNote": "Rullgardinsmenyn \"{}\" kan användas för att nå självhostade/anpassade instanser av valfri källa.",
"badDownload": "The APK could not be parsed (incompatible or partial download)", "badDownload": "APK kunde inte analyseras (inkompatibel eller partiell nedladdning)",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Ta Bort App?", "one": "Ta Bort App?",
"other": "Ta Bort Appar?" "other": "Ta Bort Appar?"

View File

@@ -298,9 +298,9 @@
"installed": "Kurulmuş", "installed": "Kurulmuş",
"latest": "En sonuncu", "latest": "En sonuncu",
"invertRegEx": "Normal ifadeyi ters çevir", "invertRegEx": "Normal ifadeyi ters çevir",
"note": "Note", "note": "Not",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", "selfHostedNote": "\"{}\" ılır menüsü, herhangi bir kaynağın kendi kendine barındırılan/özel örneklerine ulaşmak için kullanılabilir.",
"badDownload": "The APK could not be parsed (incompatible or partial download)", "badDownload": "APK ayrıştırılamadı (uyumsuz veya kısmi indirme)",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Uygulamayı Kaldır?", "one": "Uygulamayı Kaldır?",
"other": "Uygulamaları Kaldır?" "other": "Uygulamaları Kaldır?"

View File

@@ -298,9 +298,9 @@
"installed": "已安装", "installed": "已安装",
"latest": "最新的", "latest": "最新的",
"invertRegEx": "反转正则表达式", "invertRegEx": "反转正则表达式",
"note": "Note", "note": "备注",
"selfHostedNote": "The \"{}\" dropdown can be used to reach self-hosted/custom instances of any source.", "selfHostedNote": "{}\"下拉菜单可用于访问任何来源的自托管/自定义实例。",
"badDownload": "The APK could not be parsed (incompatible or partial download)", "badDownload": "无法解析 APK不兼容或部分下载",
"removeAppQuestion": { "removeAppQuestion": {
"one": "是否删除应用?", "one": "是否删除应用?",
"other": "是否删除应用?" "other": "是否删除应用?"

View File

@@ -1038,7 +1038,7 @@ class AppsPageState extends State<AppsPage> {
IconButton( IconButton(
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
style: const ButtonStyle(visualDensity: VisualDensity.compact), style: const ButtonStyle(visualDensity: VisualDensity.compact),
tooltip: isFilterOff ? tr('filter') : tr('filterActive'), tooltip: '${tr('filter')}${isFilterOff ? '' : ' *'}',
onPressed: isFilterOff onPressed: isFilterOff
? showFilterDialog ? showFilterDialog
: () { : () {