Skip to content

Commit

Permalink
[DX] Allow using br tags in translations to avoid confusion (#4380)
Browse files Browse the repository at this point in the history
  • Loading branch information
arielj authored Mar 6, 2025
1 parent c1e2261 commit 27212b1
Show file tree
Hide file tree
Showing 51 changed files with 186 additions and 67 deletions.
7 changes: 5 additions & 2 deletions i18next-parser.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ module.exports = {
tsx: [
{
attr: 'i18nKey', // Attribute for the keys
lexer: 'JsxLexer'
lexer: 'JsxLexer',
transSupportBasicHtmlNodes: true
}
]
},
Expand Down Expand Up @@ -75,6 +76,8 @@ module.exports = {
// Whether to use the keys as the default value; ex. "Hello": "Hello", "World": "World"
// This option takes precedence over the `defaultValue` and `skipDefaultValues` options

verbose: true
verbose: true,
// Display info about the parsing including some stat

transSupportBasicHtmlNodes: true
}
140 changes: 140 additions & 0 deletions meta/lintTranslations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* Script to run some checks against translations
*
* run with `pnpm lint-translations`
*
* It can flag:
* - empty strings
* - translations in language file that are not present in `en` file
* - translations that add content between indexed content-less tags
* like `<1></1>` in `en` file but with content in the translation
* - translations missing content-less tags that are present in `en`
* file, like a translation missing a `<2></2>` tag
*
* It shows a list with the different results of the checks, language
* and keys compared.
*/

import { readdirSync, readFileSync } from 'graceful-fs'
import { join } from 'path'

// there are many extra keys in translation files without a matching
// key in the english file
//
// this is not really a problem so these messages are ignored by default
const printExtraTransations = false

const localesPath = './public/locales'

// Read a file as JSON
function readFile(fileName: string, language: string) {
try {
return JSON.parse(
readFileSync(join(localesPath, language, fileName + '.json')).toString()
)
} catch (error) {
// console.log(error)
return null
}
}

// Read the 3 files for a given language
function readFiles(language: string) {
return {
gamepage: readFile('gamepage', language),
login: readFile('login', language),
translation: readFile('translation', language)
}
}

const enFiles = readFiles('en')
let processingLanguage = ''
let processingFile = ''

// Run checks in string from translation against original in english file
function checkStringValueAgainstEnglish(
trValue: string,
enValue: string,
parent?: string
) {
if (trValue === '') {
console.log(
`Empty translation for ${processingLanguage}.${processingFile}.${parent}`
)
return
}

const i18nTags = enValue.match(/<(\d+)><\/\1>/)
if (i18nTags) {
// check content-less tags like `<1></1>`
const invalidTags: string[] = []
const matches = [...enValue.matchAll(/<(\d+)><\/\1>/g)]
matches.forEach((ma) => {
const str = ma[0]
if (!trValue.includes(str)) {
invalidTags.push(str)
}
})

if (invalidTags.length) {
console.log(
`Missing content in translation, <X></X> tags not matching original for ${processingLanguage}.${processingFile}.${parent}.\nExpected ${enValue}\nGot: ${trValue}\n\n`
)
}
}
}

// Recursive function traversing objects
function checkValueAgainstEnglish(
trValue: string | object,
enValue: string | object,
parent?: string
) {
if (typeof enValue === 'undefined') {
if (printExtraTransations) {
console.log(
`Extra translation not present in english for ${processingLanguage}.${processingFile}.${parent}`
)
}
} else {
if (typeof trValue === 'string') {
checkStringValueAgainstEnglish(trValue, enValue as string, parent)
} else {
for (const key in trValue) {
checkValueAgainstEnglish(trValue[key], enValue[key], `${parent}.${key}`)
}
}
}
}

// entry point to check a single translation file
function checkFileAgainstEnglish(translations: object) {
for (const key in translations) {
checkValueAgainstEnglish(
translations[key],
enFiles[processingFile][key],
key
)
}
}

// entry point to check a single language
function checkLanguage(language: string) {
const langFiles = readFiles(language)

for (const file in langFiles) {
const content = langFiles[file]
if (!content) continue

processingFile = file
checkFileAgainstEnglish(content)
}
}

// loop through all translations and compare to english
readdirSync(localesPath).forEach((dir) => {
if (dir === 'en') return

processingLanguage = dir
checkLanguage(dir)
})
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"prepare": "husky install",
"prettier": "prettier --check .",
"prettier-fix": "prettier --write .",
"download-helper-binaries": "esbuild --bundle --platform=node --target=node21 meta/downloadHelperBinaries.ts | node"
"download-helper-binaries": "esbuild --bundle --platform=node --target=node21 meta/downloadHelperBinaries.ts | node",
"lint-translations": "esbuild --bundle --platform=node --target=node21 meta/lintTranslations.ts | node"
},
"dependencies": {
"@emotion/react": "11.10.6",
Expand Down
1 change: 0 additions & 1 deletion public/locales/ar/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,6 @@
"add-new-category": "أضف فئةً"
},
"emptyLibrary": {
"noGames": "مكتبتك فارغة. يمكنك <1>تسجيل الدخول</1> باستخدام المتجر أو النقر على <3></3> لإضافة متجر يدويًا.",
"noResults": "لم تسفر التصنيفات الحالية عن أية نتائج."
},
"wineExplanation": {
Expand Down
3 changes: 1 addition & 2 deletions public/locales/be/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -909,8 +909,7 @@
"wine-ge": "Wine-GE-Proton - гэта варыянт Wine, створаны Glorious Eggroll. Рэкамендуецца выкарыстоўваць Wine за межамі Steam. Гэта дае карысныя журналы пры выпраўленні непаладак."
},
"emptyLibrary": {
"noResults": "Бягучыя фільтры не далі вынікаў.",
"noGames": "У вашай бібліятэке пакуль нічога няма. Вы можаце <1>ўвайсці</1> з дапамогай крамы або націснуць <3></3>, каб дадаць яе ўручную."
"noResults": "Бягучыя фільтры не далі вынікаў."
},
"adtraction-locked": {
"title": "Adtraction заблакаваны",
Expand Down
4 changes: 2 additions & 2 deletions public/locales/bg/gamepage.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@
"title": "Противодействието на измами не работи",
"ok": "Добре",
"multiplayer_message": "Системата против измами не работи или е забранена. Играта може и да стартира, но функционалностите за игра в мрежа няма да работят. Искате ли да я инсталирате въпреки това?",
"disabled_installation": "Тази игра използва софтуер против измами, който не е съвместим с Вашата операционна система, или не е включен от разработчиците. Това означава, че функционалностите за игра в мрежа няма да работят, и няма нищо, което Вие (или Heroic) да може да направи. <1></1><2></2>Ако все пак искате да инсталирате тази игра, идете в разширените настройки и включете разрешението за инсталиране на игри с неработеща или забранена система против измами.<4></4><5></5>Имайте предвид, че този проблем няма решение и може да получите забрана в играта.",
"disabled_installation": "Тази игра използва софтуер против измами, който не е съвместим с Вашата операционна система, или не е включен от разработчиците. Това означава, че функционалностите за игра в мрежа няма да работят, и няма нищо, което Вие (или Heroic) да може да направи. <br /><br />Ако все пак искате да инсталирате тази игра, идете в разширените настройки и включете разрешението за инсталиране на игри с неработеща или забранена система против измами.<br /><br />Имайте предвид, че този проблем няма решение и може да получите забрана в играта.",
"install_anyway": "Да (разбирам, че функционалностите за игра в мрежа няма да работят)"
},
"flatpak-path-not-writtable": "Грешка: ограничената среда за изпълнение няма достъп до този път. Ще има загуба на данни."
Expand Down Expand Up @@ -217,7 +217,7 @@
"useragent": "Въведете персонализиран потребителски идентификатор (user agent), който да се ползва за това приложение/игра в браузъра"
},
"import-hint": {
"content": "НЕ използвайте тази функционалност за това.<1></1>Вместо това се <3>впишете</3> в магазина, намерете играта в библиотеката си, отворете прозореца за инсталиране и натиснете бутона „Внасяне на играта“",
"content": "НЕ използвайте тази функционалност за това.<br />Вместо това се <3>впишете</3> в магазина, намерете играта в библиотеката си, отворете прозореца за инсталиране и натиснете бутона „Внасяне на играта“",
"title": "Важно! Ако добавяте игра от Epic/GOG/Amazon, натиснете тук!"
}
},
Expand Down
2 changes: 1 addition & 1 deletion public/locales/bg/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -962,7 +962,7 @@
"description": "Изглежда домейнът track.adtraction.com не може да бъде зареден или е блокиран. Използвайки „adtraction“, всяка покупка, която правите в магазина на GOG, подкрепя финансово Heroic. Ако искате да помогнете на проекта, премахнете това блокиране."
},
"emptyLibrary": {
"noGames": "Библиотеката е празна.<1></1><2></2>Натиснете <4>тук</4>, за да се впишете с акаунтите си в Epic, GOG.com или Amazon. След това игрите ще се появят тук, в библиотеката.<6></6><7></7>Ако искате да пускате игри от други места, натиснете <9></9>, за да ги добавите ръчно.",
"noGames": "Библиотеката е празна.<br /><br />Натиснете <4>тук</4>, за да се впишете с акаунтите си в Epic, GOG.com или Amazon. След това игрите ще се появят тук, в библиотеката.<br /><br />Ако искате да пускате игри от други места, натиснете <br />, за да ги добавите ръчно.",
"noResults": "Няма резултати от текущите филтри."
},
"wineExplanation": {
Expand Down
4 changes: 2 additions & 2 deletions public/locales/ca/gamepage.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@
"multiplayer_message": "El suport antitrampes està trencat o s'ha blocat. El joc pot executar-se però les característiques multijugador no funcionaran. Voleu instal·lar-lo igualment?",
"install_anyway": "Sí (entenc que les funcions multijugador no funcionen)",
"ok": "D'acord",
"disabled_installation": "Aquest joc fa servir programari antitrampes que no és compatible amb el vostre sistema operatiu o els desenvolupadors del joc no n'han activat el suport. Això significa que les funcions multijugador no funcionaran i no hi ha res que pugueu fer per a resoldre-ho ‒ni tan sols l'equip de l'Heroic.<1></1><2></2>Per a instal·lar aquest joc i provar-lo de totes maneres, aneu a Configuració→Avançat i seleccioneu l'opció d'instal·lar jocs amb antitrampes trencat o denegat.<4></4><5></5>Tingueu en compte que no hi ha manera de resoldre-ho i us arrisqueu a què us expulsin de les partides."
"disabled_installation": "Aquest joc fa servir programari antitrampes que no és compatible amb el vostre sistema operatiu o els desenvolupadors del joc no n'han activat el suport. Això significa que les funcions multijugador no funcionaran i no hi ha res que pugueu fer per a resoldre-ho ‒ni tan sols l'equip de l'Heroic.<br /><br />Per a instal·lar aquest joc i provar-lo de totes maneres, aneu a Configuració→Avançat i seleccioneu l'opció d'instal·lar jocs amb antitrampes trencat o denegat.<br /><br />Tingueu en compte que no hi ha manera de resoldre-ho i us arrisqueu a què us expulsin de les partides."
},
"flatpak-path-not-writtable": "Error: L'accés lliure a aquest camí no està garantit. Potser es perden dades."
},
Expand Down Expand Up @@ -219,7 +219,7 @@
},
"import-hint": {
"title": "Important! Si voleu afegir un joc d'Epic/GOGO/Amazon, feu clic aquí.",
"content": "No feu servir aquesta funció per a això.<1></1>En el seu lloc, <3>inicieu sessió</3> a la botiga, busqueu el joc a la vostra biblioteca, obriu el diàleg d'instal·lació i feu clic al botó &quot;Importa un joc&quot;."
"content": "No feu servir aquesta funció per a això.<br />En el seu lloc, <3>inicieu sessió</3> a la botiga, busqueu el joc a la vostra biblioteca, obriu el diàleg d'instal·lació i feu clic al botó &quot;Importa un joc&quot;."
}
},
"specs": {
Expand Down
1 change: 0 additions & 1 deletion public/locales/ca/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -947,7 +947,6 @@
"wine-ge": "Wine-GE-Proton és una variant del Wine creada per Glorious Eggroll. És el Wine recomanat per a fer servir fora d'Steam. Proporciona registres útils per a la solució de problemes."
},
"emptyLibrary": {
"noGames": "La vostra biblioteca està buida. Podeu <1>iniciar sessió</1> en alguna botiga o fer clic <3></3> per a afegir-ne una manualment.",
"noResults": "Els filtres actuals no han produït cap resultat."
},
"categories-manager": {
Expand Down
4 changes: 2 additions & 2 deletions public/locales/cs/gamepage.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@
"install_anyway": "Ano (rozumím, že funkce hry více hráčů nebudou fungovat)",
"multiplayer_message": "Podpora anticheatu je rozbitá nebo odmítnutá. Hra se může otevřít, ale nebudou fungovat funkce hry více hráčů. Chcete ji přesto nainstalovat?",
"ok": "Ok",
"disabled_installation": "Tato hra používá anticheat program, který není kompatibilní s vaším operačním systémem, nebo vývojáři hry nepovolili jeho podporu. To znamená, že funkce pro více hráčů nebudou fungovat a vy (ani tým Heroic) s tím nemůžete nic udělat.<1></1><2></2>Chcete-li tuto hru přesto nainstalovat a vyzkoušet, přejděte do Nastavení, Rozšířené a zaškrtněte možnost povolit instalaci her s nefunkčním nebo zakázaným anticheatem.<4></4><5></5>Upozorňujeme, že pro tuto situaci neexistuje žádné řešení a riskujete, že budete ve hře zablokován."
"disabled_installation": "Tato hra používá anticheat program, který není kompatibilní s vaším operačním systémem, nebo vývojáři hry nepovolili jeho podporu. To znamená, že funkce pro více hráčů nebudou fungovat a vy (ani tým Heroic) s tím nemůžete nic udělat.<br /><br />Chcete-li tuto hru přesto nainstalovat a vyzkoušet, přejděte do Nastavení, Rozšířené a zaškrtněte možnost povolit instalaci her s nefunkčním nebo zakázaným anticheatem.<br /><br />Upozorňujeme, že pro tuto situaci neexistuje žádné řešení a riskujete, že budete ve hře zablokován."
},
"flatpak-path-not-writtable": "Chyba: této cestě nebyl udělen přístup k sandboxu, vyskytne se ztráta dat."
},
Expand Down Expand Up @@ -218,7 +218,7 @@
"useragent": "Sem zadejte vlastní User Agent (uživatelský agent) pro použití v této prohlížečové aplikaci/hře"
},
"import-hint": {
"content": "Tuto funkci k této akci NEPOUŽÍVEJTE.<1></1>Namísto toho se <3>přihlaste do obchodu</3>, vyhledejte hru ve své knihovně, otevřete instalační dialog a klikněte na tlačítko &quot;Importovat hru&quot;",
"content": "Tuto funkci k této akci NEPOUŽÍVEJTE.<br />Namísto toho se <3>přihlaste do obchodu</3>, vyhledejte hru ve své knihovně, otevřete instalační dialog a klikněte na tlačítko &quot;Importovat hru&quot;",
"title": "Důležité! Přidáváte hru z obchodu Epic/GOG/Amazon? Klikněte sem!"
}
},
Expand Down
2 changes: 1 addition & 1 deletion public/locales/cs/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -962,7 +962,7 @@
"wine-ge": "Wine-GE-Proton je verze Protonu od vývojáře Glorious Eggroll. Jedná se o doporučenou verzi Wine k použití mimo službu Steam. Poskytuje užitečné protokoly pro ladění chyb."
},
"emptyLibrary": {
"noGames": "Vaše knihovna je prázdná.<1></1><2></2>Klikněte <4>zde</4> a přihlaste se pomocí účtu Epic, GOG.com nebo Amazon. Poté se vaše hry zobrazí zde v knihovně.<6></6><7></7>Chcete-li používat hry nebo aplikace z jiných zdrojů, klikněte na <9></9> a přidejte je ručně.",
"noGames": "Vaše knihovna je prázdná.<br /><br />Klikněte <4>zde</4> a přihlaste se pomocí účtu Epic, GOG.com nebo Amazon. Poté se vaše hry zobrazí zde v knihovně.<br /><br />Chcete-li používat hry nebo aplikace z jiných zdrojů, klikněte na <br /> a přidejte je ručně.",
"noResults": "S nastavenými filtry nebyl nalezen žádný výsledek."
},
"adtraction-locked": {
Expand Down
2 changes: 1 addition & 1 deletion public/locales/da/gamepage.json
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@
"useragent": "Skriv en brugerdefineret agent, der kan bruger i denne browser app/spil"
},
"import-hint": {
"content": "Brug IKKE denne funktionalitet til dettte. .<1></1>Istedet, skal du <3>logge ind</3> butikken, finde spillet i dit biblotek, åben installationsdialogkassen og kikke &quot;Import Game&quot; knappen",
"content": "Brug IKKE denne funktionalitet til dettte. .<br />Istedet, skal du <3>logge ind</3> butikken, finde spillet i dit biblotek, åben installationsdialogkassen og kikke &quot;Import Game&quot; knappen",
"title": "Vigtigt! Vil du tilfølge et spil fra Epic/GOG/Amazon? Klik her!"
}
},
Expand Down
1 change: 0 additions & 1 deletion public/locales/da/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,6 @@
"new-category": "Ny Kategori"
},
"emptyLibrary": {
"noGames": "Dit biblotek er tom. Du kan <1>logge ind</1> igennem en butik eller ved at klikke <3></3> for manuelt at tilføje et.",
"noResults": "De valgte filter produceret ingen resultater."
}
}
1 change: 0 additions & 1 deletion public/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,6 @@
"proton-ge": "GE-Proton ist eine Variante von Proton, geschaffen von Glorious Eggroll. Obwohl diese Variante für Steam gedacht ist funktionieren einige Spiele außerhalb Steams trotzdem besser mit dieser. Es stellt jedoch größtenteils nutzlose Fehlerprotokolle bereit."
},
"emptyLibrary": {
"noGames": "Ihre Bibliothek ist leer. Sie können sich durch/bei einen Store <1>einloggen</1> oder auf <3></3> klicken, um manuell ein Spiel hinzuzufügen.",
"noResults": "Die aktuellen Filter haben keine Suchergebnisse hervorgebracht."
},
"categories-manager": {
Expand Down
1 change: 0 additions & 1 deletion public/locales/el/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,6 @@
"title": "Διαχείριση κατηγοριών"
},
"emptyLibrary": {
"noGames": "Η βιβλιοθήκη σου είναι άδεια. Μπορείτε να <1>Συνδεθείτε</1> χρησιμοποιώντας ένα κατάστημα ή κλικ <3></3> για να προσθέσετε ένα με μη αυτόματο τρόπο.",
"noResults": "Τα τρέχοντα φίλτρα δεν παρήγαγαν κανένα αποτέλεσμα."
},
"window": {
Expand Down
Loading

0 comments on commit 27212b1

Please sign in to comment.