PICARD-1929: Convert NSIS translations to/from JSON for Transifex

This commit is contained in:
Philipp Wolfer
2020-08-28 09:01:22 +02:00
parent 2ebd5c9a7a
commit e69275e85f
8 changed files with 304 additions and 0 deletions

57
installer/languages/json2nsh.py Executable file
View File

@@ -0,0 +1,57 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Picard, the next-generation MusicBrainz tagger
#
# Copyright (C) 2020 Philipp Wolfer
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import glob
import json
import os.path
import nshutil as nsh
def language_from_filename(path):
lang = os.path.splitext(os.path.basename(path))[0]
return (nsh.code_to_language(lang), lang)
def write_langstring(f, language, identifier, text):
langstring = nsh.make_langstring(language, identifier, text)
f.write(langstring)
def main():
scriptdir = os.path.dirname(os.path.abspath(__file__))
sourcesdir = os.path.join(scriptdir, 'sources')
for path in glob.glob(os.path.join(sourcesdir, '*.json')):
language, language_code = language_from_filename(path)
if not language:
print(f'Unknown language code "{language_code}", skipping')
continue
target_file = os.path.join(scriptdir, f'{language}.nsh')
with open(path, 'r', encoding='utf-8') as infile:
data = json.loads(infile.read())
with open(target_file, 'w+', encoding='utf-8') as outfile:
for identifier, text in data.items():
write_langstring(outfile, language, identifier, text)
if __name__ == "__main__":
main()

60
installer/languages/nsh2json.py Executable file
View File

@@ -0,0 +1,60 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Picard, the next-generation MusicBrainz tagger
#
# Copyright (C) 2020 Philipp Wolfer
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import glob
import json
import os.path
import nshutil as nsh
def language_from_filename(path):
lang = os.path.splitext(os.path.basename(path))[0]
return (lang, nsh.language_to_code(lang))
def extract_strings(f):
for line in f:
parsed = nsh.parse_langstring(line)
if parsed:
yield parsed
def main():
scriptdir = os.path.dirname(os.path.abspath(__file__))
for path in glob.glob(os.path.join(scriptdir, '*.nsh')):
language, language_code = language_from_filename(path)
if not language_code:
print(f'Unknown language "{language}", skipping')
continue
target_file = os.path.join(scriptdir, 'sources', f'{language_code}.json')
with open(path, 'r', encoding='utf-8') as infile:
output = {}
for identifier, text in extract_strings(infile):
output[identifier] = text
with open(target_file, 'w+', encoding='utf-8') as outfile:
outfile.write(json.dumps(output, indent=4))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,107 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Picard, the next-generation MusicBrainz tagger
#
# Copyright (C) 2020 Philipp Wolfer
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import re
LANGUAGES = {
'Arabic': 'ar',
'Catalan': 'ca',
'Czech': 'cs',
'Danish': 'da',
'Dutch': 'nl',
'English': 'en',
'Estonian': 'et',
'Finnish': 'fi',
'French': 'fr',
'German': 'de',
'Greek': 'el',
'Hebrew': 'he',
'Italian': 'it',
'Japanese': 'ja',
'Korean': 'ko',
'Norwegian': 'nb',
'Polish': 'pl',
'Portuguese': 'pt',
'PortugueseBR': 'pt_BR',
'Russian': 'ru',
'SimpChinese': 'zh-Hans',
'Slovak': 'sk',
'Slovenian': 'sl',
'Spanish': 'es',
'Swedish': 'sv',
'TradChinese': 'zh-Hant',
'Turkish': 'tr',
'Ukrainian': 'uk',
}
_R_LANGUAGES = dict([(code, name) for name, code in LANGUAGES.items()])
# See https://nsis.sourceforge.io/Docs/Chapter4.html#varstrings
ESCAPE_CHARS = {
r'$\r': '\r',
r'$\n': '\n',
r'$\t': '\t',
r'$\"': '"',
r'$\'': "'",
r'$\`': '`',
}
RE_LANGSTRING_LINE = re.compile(r'LangString\s+(?P<identifier>[A-Za-z0-9_]+)\s+\${LANG_[A-Z]+}\s+["\'`](?P<text>.*)["\'`]$')
def language_to_code(language):
return LANGUAGES.get(language)
def code_to_language(language_code):
return _R_LANGUAGES.get(language_code)
def escape_string(text):
for escape, char in ESCAPE_CHARS.items():
if char in ("'", "`"): # No need to escape quotes other than ""
continue
text = text.replace(char, escape)
return text
def unescape_string(text):
for escape, char in ESCAPE_CHARS.items():
text = text.replace(escape, char)
return text
def parse_langstring(line):
match = RE_LANGSTRING_LINE.match(line)
if match:
return (
match.group('identifier'),
unescape_string(match.group('text'))
)
else:
return None
def make_langstring(language, identifier, text):
language = language.upper()
text = escape_string(text)
return f'LangString {identifier} ${{LANG_{language}}} "{text}"\n'

View File

@@ -0,0 +1,16 @@
{
"MsgAlreadyInstalled": "${PRODUCT_NAME} ist bereits installiert. \n\nKlicken Sie \u201eOK\u201c um die vorherige Version zu deinstallieren oder \u201eAbbrechen\u201c um das Update abzubrechen.",
"MsgApplicationRunning": "Die Anwendung ${PRODUCT_NAME} wird ausgef\u00fchrt. Bitte schlie\u00dfen und erneut versuchen.",
"MsgRequires64Bit": "Diese Version von ${PRODUCT_NAME} erfordert ein 64-bit Windows-System.",
"MuiDescriptionRequired": "Installiert ${PRODUCT_NAME} mit den f\u00fcr die Ausf\u00fchrung erforderlichen Dateien.",
"MuiDescriptionLang": "Installiert \u00dcbersetzungen von ${PRODUCT_NAME} in verschiedenen Sprachen.",
"MuiDescriptionShortcuts": "Installiert Verkn\u00fcpfungen, um ${PRODUCT_NAME} zu starten.",
"MuiDescriptionDesktop": "Installiert eine Verkn\u00fcpfung auf dem Desktop.",
"MuiDescriptionStarteMenu": "Installiert eine Verkn\u00fcpfung im Startmen\u00fc.",
"OptionRemoveSettings": "Einstellungen und pers\u00f6nliche Daten entfernen",
"SectionDesktop": "Desktop",
"SectionLanguages": "Sprachen",
"SectionRequired": "Programmdateien (erforderlich)",
"SectionShortcuts": "Verkn\u00fcpfungen",
"SectionStartMenu": "Startmen\u00fc"
}

View File

@@ -0,0 +1,16 @@
{
"MsgAlreadyInstalled": "${PRODUCT_NAME} is already installed. \n\nClick \"OK\" to uninstall the previous version or \"Cancel\" to cancel this upgrade.",
"MsgApplicationRunning": "The application ${PRODUCT_NAME} is running. Please close it and try again.",
"MsgRequires64Bit": "This version of ${PRODUCT_NAME} requires a 64-bit Windows system.",
"MuiDescriptionRequired": "Installs ${PRODUCT_NAME} along with the necessary files for it run.",
"MuiDescriptionLang": "Installs translations of ${PRODUCT_NAME} in different languages.",
"MuiDescriptionShortcuts": "Installs shortcuts to launch ${PRODUCT_NAME}.",
"MuiDescriptionDesktop": "Installs a shortcut on the desktop.",
"MuiDescriptionStarteMenu": "Installs a shortcut in the Start Menu.",
"OptionRemoveSettings": "Remove settings and personal data",
"SectionDesktop": "Desktop",
"SectionLanguages": "Languages",
"SectionRequired": "Program files (required)",
"SectionShortcuts": "Shortcuts",
"SectionStartMenu": "Start menu"
}

View File

@@ -0,0 +1,16 @@
{
"MsgAlreadyInstalled": "${PRODUCT_NAME} est d\u00e9j\u00e0 install\u00e9. \n\nCliquez sur \u00ab\u00a0OK\u00a0\u00bb pour d\u00e9sinstaller la version pr\u00e9c\u00e9dente ou \u00ab\u00a0Annuler\u00a0\u00bb pour annuler cette mise \u00e0 jour.",
"MsgApplicationRunning": "L\u2019application ${PRODUCT_NAME} est en cours d\u2019ex\u00e9cution. Veuillez la stopper et essayer \u00e0 nouveau.",
"MsgRequires64Bit": "Cette version de ${PRODUCT_NAME} requiert un syst\u00e8me Windows 64 bits.",
"MuiDescriptionRequired": "Installe ${PRODUCT_NAME} ainsi que les fichiers n\u00e9cessaires \u00e0 son ex\u00e9cution.",
"MuiDescriptionLang": "Installe les traductions de ${PRODUCT_NAME} dans diff\u00e9rentes langues.",
"MuiDescriptionShortcuts": "Installe les raccourcis pour lancer ${PRODUCT_NAME}.",
"MuiDescriptionDesktop": "Installe un raccourci sur le bureau.",
"MuiDescriptionStarteMenu": "Installe un raccourci dans le menu D\u00e9marrer.",
"OptionRemoveSettings": "Supprimer les pr\u00e9f\u00e9rences et les donn\u00e9es personnelles",
"SectionDesktop": "Bureau",
"SectionLanguages": "Langues",
"SectionRequired": "Fichiers du programme (requis)",
"SectionShortcuts": "Raccourcis",
"SectionStartMenu": "Menu D\u00e9marrer"
}

View File

@@ -0,0 +1,16 @@
{
"MsgAlreadyInstalled": "${PRODUCT_NAME} \u00e8 gi\u00e0 installato.\n\nFai clic su 'OK' per disinstallare la versione installata o su 'Annulla' per annullare l'aggiornamento.",
"MsgApplicationRunning": "L'applicazione ${PRODUCT_NAME} \u00e8 in esecuzione.\nChiudi l'applicazione e riprova.",
"MsgRequires64Bit": "Questa versione di ${PRODUCT_NAME} richiede un sistema Windows a 64bit.",
"MuiDescriptionRequired": "Installa ${PRODUCT_NAME} e i relativi file necessari alla sua esecuzione.",
"MuiDescriptionLang": "Installa le traduzioni di ${PRODUCT_NAME} per le differenti lingue.",
"MuiDescriptionShortcuts": "Installa collegamento per eseguire ${PRODUCT_NAME}.",
"MuiDescriptionDesktop": "Installa collegamento sul desktop.",
"MuiDescriptionStarteMenu": "Installa collegamento nel menu Start.",
"OptionRemoveSettings": "Elimina impostazioni e dati personali",
"SectionDesktop": "Desktop",
"SectionLanguages": "Lingue",
"SectionRequired": "File programma (richiesti)",
"SectionShortcuts": "Collegamenti",
"SectionStartMenu": "Menu Start"
}

View File

@@ -0,0 +1,16 @@
{
"MsgAlreadyInstalled": "${PRODUCT_NAME} is al ge\u00efnstalleerd. \n\nKlik op \u201cOK\u201d om de vorige versie te verwijderen of op \u201cAnnuleren\u201d om de installatie te annuleren.",
"MsgApplicationRunning": "De applicatie ${PRODUCT_NAME} is nog actief. Sluit haar af en probeer het opnieuw.",
"MsgRequires64Bit": "Voor deze versie van ${PRODUCT_NAME} heb je een 64 bitssysteem nodig.",
"MuiDescriptionRequired": "Installeert ${PRODUCT_NAME} en de bestanden die het nodig heeft om te draaien.",
"MuiDescriptionLang": "Installeert vertalingen van ${PRODUCT_NAME}.",
"MuiDescriptionShortcuts": "Installeert snelkoppelingen om ${PRODUCT_NAME} te starten.",
"MuiDescriptionDesktop": "Installeert een snelkoppeling op het bureaublad.",
"MuiDescriptionStarteMenu": "Installeert een snelkoppeling in het startmenu.",
"OptionRemoveSettings": "Verwijder instellingen en persoonlijke gegevens",
"SectionDesktop": "Bureaublad",
"SectionLanguages": "Talen",
"SectionRequired": "Programmabestanden (vereist)",
"SectionShortcuts": "Snelkoppelingen",
"SectionStartMenu": "Startmenu"
}