diff --git a/.gitignore b/.gitignore index 3d1b337ed..5d442ef0f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ *.egg-info setup.iss novelwriter.desktop +i18n/*.qm +i18n/*.qph # Documentation /docs/build/ diff --git a/i18n/nw_pt.qm b/i18n/nw_pt.qm deleted file mode 100644 index 81046adfb..000000000 Binary files a/i18n/nw_pt.qm and /dev/null differ diff --git a/i18n/phrases_pt.qph b/i18n/phrases_pt.qph deleted file mode 100644 index 7b58cb0bf..000000000 --- a/i18n/phrases_pt.qph +++ /dev/null @@ -1,447 +0,0 @@ -<!DOCTYPE QPH> -<QPH sourcelanguage="en" language="pt"> -<phrase> - <source>Point of View</source> - <target>Ponto de Vista</target> -</phrase> -<phrase> - <source>Characters</source> - <target>Personagens</target> -</phrase> -<phrase> - <source>Plot</source> - <target>Enredo</target> -</phrase> -<phrase> - <source>Timeline</source> - <target>Linha do Tempo</target> -</phrase> -<phrase> - <source>Locations</source> - <target>Lugares</target> -</phrase> -<phrase> - <source>Objects</source> - <target>Objetos</target> -</phrase> -<phrase> - <source>Entities</source> - <target>Entidades</target> -</phrase> -<phrase> - <source>None</source> - <target>Nenhum</target> -</phrase> -<phrase> - <source>Novel</source> - <target>Livro</target> -</phrase> -<phrase> - <source>Entity</source> - <target>Entidade</target> -</phrase> -<phrase> - <source>Outtakes</source> - <target>Removidos</target> -</phrase> -<phrase> - <source>Trash</source> - <target>Lixeira</target> -</phrase> -<phrase> - <source>Title Page</source> - <target>Página de Título</target> -</phrase> -<phrase> - <source>Book</source> - <target>Livro</target> -</phrase> -<phrase> - <source>Plain Page</source> - <target>Página</target> -</phrase> -<phrase> - <source>Partition</source> - <target>Partição</target> -</phrase> -<phrase> - <source>Unnumbered</source> - <target>Sem Numeração</target> -</phrase> -<phrase> - <source>Scene</source> - <target>Cena</target> -</phrase> -<phrase> - <source>Note</source> - <target>Nota</target> -</phrase> -<phrase> - <source>Title</source> - <target>Título</target> -</phrase> -<phrase> - <source>Level</source> - <target>Nível</target> -</phrase> -<phrase> - <source>Document</source> - <target>Documento</target> -</phrase> -<phrase> - <source>Line</source> - <target>Linha</target> -</phrase> -<phrase> - <source>Chars</source> - <target>Caracteres</target> -</phrase> -<phrase> - <source>Words</source> - <target>Palavras</target> -</phrase> -<phrase> - <source>Synopsis</source> - <target>Sinopse</target> -</phrase> -<phrase> - <source>About</source> - <target>Sobre</target> -</phrase> -<phrase> - <source>Release</source> - <target>Versões</target> -</phrase> -<phrase> - <source>About novelWriter</source> - <target>Sobre o novelWriter</target> -</phrase> -<phrase> - <source>Credits</source> - <target>Créditos</target> -</phrase> -<phrase> - <source>Author</source> - <target>Autor</target> -</phrase> -<phrase> - <source>Credit</source> - <target>Créditos</target> -</phrase> -<phrase> - <source>License</source> - <target>Licença</target> -</phrase> -<phrase> - <source>Theme</source> - <target>Tema</target> -</phrase> -<phrase> - <source>Icons</source> - <target>Ícones</target> -</phrase> -<phrase> - <source>Syntax</source> - <target>Sintaxe</target> -</phrase> -<phrase> - <source>Website</source> - <target>Website</target> -</phrase> -<phrase> - <source>Chapter</source> - <target>Capítulo</target> -</phrase> -<phrase> - <source>Section</source> - <target>Seção</target> -</phrase> -<phrase> - <source>Font family</source> - <target>Família da fonte</target> -</phrase> -<phrase> - <source>Font size</source> - <target>Tamanho da fonte</target> -</phrase> -<phrase> - <source>Justify text</source> - <target>Texto justificado</target> -</phrase> -<phrase> - <source>Print</source> - <target>Imprimir</target> -</phrase> -<phrase> - <source>Build Project</source> - <target>Construir o Projeto</target> -</phrase> -<phrase> - <source>Save As</source> - <target>Salvar Como</target> -</phrase> -<phrase> - <source>Close</source> - <target>Fechar</target> -</phrase> -<phrase> - <source>Plain Text</source> - <target>Texto Simples</target> -</phrase> -<phrase> - <source>Plain HTML</source> - <target>HTML Simples</target> -</phrase> -<phrase> - <source>Save Document As</source> - <target>Salvar Documento Como</target> -</phrase> -<phrase> - <source>Unknown</source> - <target>Desconhecido</target> -</phrase> -<phrase> - <source>Look and Feel</source> - <target>Aparência</target> -</phrase> -<phrase> - <source>Project Backup</source> - <target>Cópia de Segurança</target> -</phrase> -<phrase> - <source>Path</source> - <target>Caminho</target> -</phrase> -<phrase> - <source>Status</source> - <target>Estado</target> -</phrase> -<phrase> - <source>Replace</source> - <target>Substituir</target> -</phrase> -<phrase> - <source>Search</source> - <target>Pesquisa</target> -</phrase> -<phrase> - <source>Handle</source> - <target>Referência</target> -</phrase> -<phrase> - <source>References</source> - <target>Referências</target> -</phrase> -<phrase> - <source>Label</source> - <target>Rótulo</target> -</phrase> -<phrase> - <source>Class</source> - <target>Classe</target> -</phrase> -<phrase> - <source>Layout</source> - <target>Leiaute</target> -</phrase> -<phrase> - <source> Characters</source> - <target>Caracteres</target> -</phrase> -<phrase> - <source>Editor</source> - <target>Editor</target> -</phrase> -<phrase> - <source>Project</source> - <target>Projeto</target> -</phrase> -<phrase> - <source>Provider</source> - <target>Provedor</target> -</phrase> -<phrase> - <source>unknown</source> - <target>desconhecido</target> -</phrase> -<phrase> - <source>Paragraphs</source> - <target>Parágrafos</target> -</phrase> -<phrase> - <source>Default</source> - <target>Padrão</target> -</phrase> -<phrase> - <source>Working title</source> - <target>Nome do projeto</target> -</phrase> -<phrase> - <source>Project path</source> - <target>Caminho do projeto</target> -</phrase> -<phrase> - <source>Project Stats</source> - <target>Estatíticas do Projeto</target> -</phrase> -<phrase> - <source>Folders</source> - <target>Diretórios</target> -</phrase> -<phrase> - <source>Documents</source> - <target>Documentos</target> -</phrase> -<phrase> - <source>Word count</source> - <target>Contagem de palavras</target> -</phrase> -<phrase> - <source>Keyword</source> - <target>Palavra-chave</target> -</phrase> -<phrase> - <source>New</source> - <target>Novo</target> -</phrase> -<phrase> - <source>Delete</source> - <target>Remover</target> -</phrase> -<phrase> - <source>Save</source> - <target>Salvar</target> -</phrase> -<phrase> - <source>Name</source> - <target>Nome</target> -</phrase> -<phrase> - <source>New Item</source> - <target>Novo Item</target> -</phrase> -<phrase> - <source>Last Opened</source> - <target>Aberto Pela Última Vez</target> -</phrase> -<phrase> - <source>Settings</source> - <target>Configurações</target> -</phrase> -<phrase> - <source>Details</source> - <target>Detalhes</target> -</phrase> -<phrase> - <source>Importance</source> - <target>Importância</target> -</phrase> -<phrase> - <source>Auto-Replace</source> - <target>Substituir automaticamente</target> -</phrase> -<phrase> - <source>Flag</source> - <target>Opção</target> -</phrase> -<phrase> - <source>Flags</source> - <target>Opções</target> -</phrase> -<phrase> - <source>(New Entry)</source> - <target></target> -</phrase> -<phrase> - <source>New File</source> - <target>Novo Arquivo</target> -</phrase> -<phrase> - <source>New Folder</source> - <target>Novo Diretório</target> -</phrase> -<phrase> - <source>Histogram</source> - <target>Histograma</target> -</phrase> -<phrase> - <source>Finished</source> - <target>Finalizado</target> -</phrase> -<phrase> - <source>Done</source> - <target>Pronto</target> -</phrase> -<phrase> - <source>Finish</source> - <target>Terminar</target> -</phrase> -<phrase> - <source>Auto-Replace</source> - <target>Substituição Automática</target> -</phrase> -<phrase> - <source>No Suggestions</source> - <target>Sem Sugestões</target> -</phrase> -<phrase> - <source>Browse</source> - <target>Procurar</target> -</phrase> -<phrase> - <source>Tag</source> - <target>Etiqueta</target> -</phrase> -<phrase> - <source>Draft</source> - <target>Rascunho</target> -</phrase> -<phrase> - <source>Minor</source> - <target>Menor</target> -</phrase> -<phrase> - <source>Major</source> - <target>Maior</target> -</phrase> -<phrase> - <source>Backup</source> - <target>Cópia de Segurança</target> -</phrase> -<phrase> - <source>Undo</source> - <target>Desfazer</target> -</phrase> -<phrase> - <source>Release</source> - <target>Lançamento</target> -</phrase> -<phrase> - <source>Pages</source> - <target>Páginas</target> -</phrase> -<phrase> - <source>Page</source> - <target>Página</target> -</phrase> -<phrase> - <source>Progress</source> - <target>Progresso</target> -</phrase> -<phrase> - <source>Chapters</source> - <target>Capítulos</target> -</phrase> -<phrase> - <source>Scenes</source> - <target>Cenas</target> -</phrase> -<phrase> - <source>Revisions</source> - <target>Revisões</target> -</phrase> -<phrase> - <source>seconds</source> - <target>segundos</target> -</phrase> -</QPH> diff --git a/nw/config.py b/nw/config.py index 5cf71b85a..618bfd3c1 100644 --- a/nw/config.py +++ b/nw/config.py @@ -393,6 +393,25 @@ def initLocalisation(self, nwApp): return + def listLanguages(self): + """List localisation files in the i18n folder. The default GUI + language 'en_GB' is British English. + """ + langList = { + "en_GB": QLocale("en_GB").nativeLanguageName().title() + } + for qmFile in os.listdir(self.nwLangPath): + if not os.path.isfile(os.path.join(self.nwLangPath, qmFile)): + continue + if not qmFile.startswith("nw_") or not qmFile.endswith(".qm"): + continue + qmLang = qmFile[3:-3] + qmName = QLocale(qmLang).nativeLanguageName().title() + if qmLang and qmName: + langList[qmLang] = qmName + + return sorted(langList.items(), key=lambda x: x[0]) + def loadConfig(self): """Load preferences from file and replace default settings. """ diff --git a/nw/gui/preferences.py b/nw/gui/preferences.py index 8234ebf38..aa7946d69 100644 --- a/nw/gui/preferences.py +++ b/nw/gui/preferences.py @@ -137,10 +137,27 @@ def __init__(self, theParent): # Look and Feel # ============= self.mainForm.addGroupLabel(self.tr("Look and Feel")) + minWidth = self.mainConf.pxInt(200) + + ## Select Locale + self.guiLang = QComboBox() + self.guiLang.setMinimumWidth(minWidth) + self.theLangs = self.mainConf.listLanguages() + for lang, langName in self.theLangs: + self.guiLang.addItem(langName, lang) + langIdx = self.guiLang.findData(self.mainConf.guiLang) + if langIdx != -1: + self.guiLang.setCurrentIndex(langIdx) + + self.mainForm.addRow( + self.tr("Main GUI language"), + self.guiLang, + self.tr("Changing this requires restarting novelWriter.") + ) ## Select Theme self.guiTheme = QComboBox() - self.guiTheme.setMinimumWidth(self.mainConf.pxInt(200)) + self.guiTheme.setMinimumWidth(minWidth) self.theThemes = self.theTheme.listThemes() for themeDir, themeName in self.theThemes: self.guiTheme.addItem(themeName, themeDir) @@ -156,7 +173,7 @@ def __init__(self, theParent): ## Select Icon Theme self.guiIcons = QComboBox() - self.guiIcons.setMinimumWidth(self.mainConf.pxInt(200)) + self.guiIcons.setMinimumWidth(minWidth) self.theIcons = self.theTheme.theIcons.listThemes() for iconDir, iconName in self.theIcons: self.guiIcons.addItem(iconName, iconDir) @@ -240,6 +257,7 @@ def __init__(self, theParent): def saveValues(self): """Save the values set for this tab. """ + guiLang = self.guiLang.currentData() guiTheme = self.guiTheme.currentData() guiIcons = self.guiIcons.currentData() guiDark = self.guiDark.isChecked() @@ -248,12 +266,14 @@ def saveValues(self): # Check if restart is needed needsRestart = False + needsRestart |= self.mainConf.guiLang != guiLang needsRestart |= self.mainConf.guiTheme != guiTheme needsRestart |= self.mainConf.guiIcons != guiIcons needsRestart |= self.mainConf.guiDark != guiDark needsRestart |= self.mainConf.guiFont != guiFont needsRestart |= self.mainConf.guiFontSize != guiFontSize + self.mainConf.guiLang = guiLang self.mainConf.guiTheme = guiTheme self.mainConf.guiIcons = guiIcons self.mainConf.guiDark = guiDark diff --git a/tests/conftest.py b/tests/conftest.py index 57956aff6..59fd5c5d4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -61,6 +61,14 @@ def refDir(): theDir = os.path.join(testDir, "reference") return theDir +@pytest.fixture(scope="session") +def filesDir(): + """The folder where additional test files are stored. + """ + testDir = os.path.dirname(__file__) + theDir = os.path.join(testDir, "files") + return theDir + @pytest.fixture(scope="session") def outDir(tmpDir): """An output folder for test results diff --git a/tests/dummy.py b/tests/dummy.py index 72ac5e21b..653fca584 100644 --- a/tests/dummy.py +++ b/tests/dummy.py @@ -91,6 +91,16 @@ def setStatus(self, theText): # END Class DummyStatusBar +class DummyApp: + + def __init__(self): + return + + def installTranslator(self, theLang): + return + +# END Class DummyApp + # =========================================================================== # # Error Functions # Dummy functions that will raise errors instead. diff --git a/tests/files/nw_en_GB.qm b/tests/files/nw_en_GB.qm new file mode 100644 index 000000000..be651eede --- /dev/null +++ b/tests/files/nw_en_GB.qm @@ -0,0 +1 @@ +<�d��!�`��� \ No newline at end of file diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py index 10ca038dd..1197ec196 100644 --- a/tests/test_base/test_base_config.py +++ b/tests/test_base/test_base_config.py @@ -27,8 +27,8 @@ from shutil import copyfile -from dummy import causeOSError -from tools import cmpFiles +from dummy import causeOSError, DummyApp +from tools import cmpFiles, writeFile from nw.config import Config from nw.constants import nwConst, nwFiles @@ -85,7 +85,7 @@ def testBaseConfig_Constructor(monkeypatch): # END Test testBaseConfig_Constructor @pytest.mark.base -def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir): +def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir): """Test config intialisation. """ tstConf = Config() @@ -192,6 +192,21 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir): tstConf.doReplaceSQuote = orDoSng assert tstConf.saveConfig() + # Localisation + i18nDir = os.path.join(fncDir, "i18n") + os.mkdir(i18nDir) + os.mkdir(os.path.join(i18nDir, "stuff")) + tstConf.nwLangPath = i18nDir + + copyfile(os.path.join(filesDir, "nw_en_GB.qm"), os.path.join(fncDir, "nw_en_GB.qm")) + writeFile(os.path.join(i18nDir, "nw_en_GB.ts"), "") + writeFile(os.path.join(i18nDir, "nw_abcd.qm"), "") + + tstApp = DummyApp() + tstConf.initLocalisation(tstApp) + theList = tstConf.listLanguages() + assert theList == [("en_GB", "British English")] + copyfile(confFile, testFile) assert cmpFiles(testFile, compFile, [2, 9, 10])