From 98e3343808473768a9d2b9060679364a49b7668d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20K=C3=BChnen=20Meneguello?= <1322552+bkmeneguello@users.noreply.github.com> Date: Tue, 9 Feb 2021 10:12:59 -0300 Subject: [PATCH] novelWriter i18n and portuguese translation --- novelWriter.pro | 36 + nw/__init__.py | 31 + nw/constants/constants.py | 123 +- nw/constants/iso.py | 869 ++--- nw/core/document.py | 21 +- nw/core/index.py | 11 +- nw/core/project.py | 234 +- nw/core/spellcheck.py | 9 +- nw/core/tohtml.py | 12 +- nw/core/tokenizer.py | 14 +- nw/core/tools.py | 2 +- nw/core/tree.py | 12 +- nw/error.py | 1 + nw/gui/about.py | 137 +- nw/gui/build.py | 176 +- nw/gui/custom.py | 2 + nw/gui/doceditor.py | 147 +- nw/gui/docmerge.py | 16 +- nw/gui/docsplit.py | 41 +- nw/gui/docviewer.py | 45 +- nw/gui/itemdetails.py | 26 +- nw/gui/itemeditor.py | 17 +- nw/gui/mainmenu.py | 465 +-- nw/gui/noveltree.py | 12 +- nw/gui/outline.py | 13 +- nw/gui/outlinedetails.py | 61 +- nw/gui/preferences.py | 279 +- nw/gui/projdetails.py | 48 +- nw/gui/projload.py | 35 +- nw/gui/projsettings.py | 82 +- nw/gui/projtree.py | 94 +- nw/gui/projwizard.py | 104 +- nw/gui/statusbar.py | 18 +- nw/gui/theme.py | 6 +- nw/gui/wordlist.py | 15 +- nw/gui/writingstats.py | 57 +- nw/guimain.py | 120 +- nw/languages/nw_pt.qm | Bin 0 -> 145009 bytes nw/languages/nw_pt.ts | 7067 +++++++++++++++++++++++++++++++++++ nw/languages/phrases_pt.qph | 447 +++ setup.py | 22 + 41 files changed, 9379 insertions(+), 1548 deletions(-) create mode 100644 novelWriter.pro create mode 100644 nw/languages/nw_pt.qm create mode 100644 nw/languages/nw_pt.ts create mode 100644 nw/languages/phrases_pt.qph diff --git a/novelWriter.pro b/novelWriter.pro new file mode 100644 index 00000000..587eea36 --- /dev/null +++ b/novelWriter.pro @@ -0,0 +1,36 @@ +SOURCES += nw/error.py \ + nw/constants/constants.py \ + nw/constants/iso.py \ + nw/core/document.py \ + nw/core/index.py \ + nw/core/project.py \ + nw/core/tohtml.py \ + nw/core/tokenizer.py \ + nw/core/tree.py \ + nw/guimain.py \ + nw/gui/about.py \ + nw/gui/build.py \ + nw/gui/custom.py \ + nw/gui/doceditor.py \ + nw/gui/dochighlight.py \ + nw/gui/docmerge.py \ + nw/gui/docsplit.py \ + nw/gui/docviewer.py \ + nw/gui/itemdetails.py \ + nw/gui/itemeditor.py \ + nw/gui/mainmenu.py \ + nw/gui/noveltree.py \ + nw/gui/outlinedetails.py \ + nw/gui/outline.py \ + nw/gui/preferences.py \ + nw/gui/projdetails.py \ + nw/gui/projload.py \ + nw/gui/projsettings.py \ + nw/gui/projtree.py \ + nw/gui/projwizard.py \ + nw/gui/statusbar.py \ + nw/gui/theme.py \ + nw/gui/wordlist.py \ + nw/gui/writingstats.py + +TRANSLATIONS += nw/languages/nw_pt.ts diff --git a/nw/__init__.py b/nw/__init__.py index 0e20cc9c..3e362cd5 100644 --- a/nw/__init__.py +++ b/nw/__init__.py @@ -24,10 +24,13 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ +import os import sys import getopt import logging +import re +from PyQt5.QtCore import QLibraryInfo, QLocale, QTranslator from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QApplication, QErrorMessage @@ -109,6 +112,21 @@ logger = logging.getLogger(__name__) # Load the main config as a global object CONFIG = Config() +nw_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "languages") +qt_path = QLibraryInfo.location(QLibraryInfo.TranslationsPath) + +translators = {} +def load_translation(app, path, prefix, lang, script = None, country = None): + filename = "_".join(filter(bool, [prefix, + lang and lang.lower(), + script and script.capitalize(), + country and country.upper()])) + if filename not in translators: + translator = QTranslator() + if translator.load(filename, path): + print(filename, path) + app.installTranslator(translator) + translators[filename] = translator def main(sysArgs=None): """Parses command line, sets up logging, and launches main GUI. @@ -284,6 +302,19 @@ def main(sysArgs=None): # Connect the exception handler before making the main GUI sys.excepthook = exceptionHandler + # Load translations + lang, script, country = re.match( + r"^([a-z]{2,3})(?:_([a-z]{4}))?(?:_([a-z]{2,3}))?$", + QLocale.system().name(), re.IGNORECASE).groups() + + for path, prefix in ((qt_path, "qt"), + (qt_path, "qtbase"), + (nw_path, "nw")): + load_translation(nwApp, path, prefix, lang) + load_translation(nwApp, path, prefix, lang, script=script) + load_translation(nwApp, path, prefix, lang, country=country) + load_translation(nwApp, path, prefix, lang, script, country) + # Launch main GUI nwGUI = GuiMain() if not nwGUI.hasProject: diff --git a/nw/constants/constants.py b/nw/constants/constants.py index 94c66244..2cfa4d02 100644 --- a/nw/constants/constants.py +++ b/nw/constants/constants.py @@ -24,6 +24,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ +from PyQt5.QtCore import QT_TRANSLATE_NOOP from nw.constants.enum import ( nwItemClass, nwItemLayout, nwItemType, nwOutline ) @@ -41,8 +42,8 @@ class nwConst(): MAX_BUILDSIZE = 10000000 # Maxium size of a project build # Spell Check Providers - SP_INTERNAL = "internal" - SP_ENCHANT = "enchant" + SP_INTERNAL = QT_TRANSLATE_NOOP("Constant", "internal") + SP_ENCHANT = QT_TRANSLATE_NOOP("Constant", "enchant") # END Class nwConst @@ -119,17 +120,17 @@ class nwKeyWords: class nwLabels(): CLASS_NAME = { - nwItemClass.NO_CLASS : "None", - nwItemClass.NOVEL : "Novel", - nwItemClass.PLOT : "Plot", - nwItemClass.CHARACTER : "Characters", - nwItemClass.WORLD : "Locations", - nwItemClass.TIMELINE : "Timeline", - nwItemClass.OBJECT : "Objects", - nwItemClass.ENTITY : "Entity", - nwItemClass.CUSTOM : "Custom", - nwItemClass.ARCHIVE : "Outtakes", - nwItemClass.TRASH : "Trash", + nwItemClass.NO_CLASS : QT_TRANSLATE_NOOP("Constant", "None"), + nwItemClass.NOVEL : QT_TRANSLATE_NOOP("Constant", "Novel"), + nwItemClass.PLOT : QT_TRANSLATE_NOOP("Constant", "Plot"), + nwItemClass.CHARACTER : QT_TRANSLATE_NOOP("Constant", "Characters"), + nwItemClass.WORLD : QT_TRANSLATE_NOOP("Constant", "Locations"), + nwItemClass.TIMELINE : QT_TRANSLATE_NOOP("Constant", "Timeline"), + nwItemClass.OBJECT : QT_TRANSLATE_NOOP("Constant", "Objects"), + nwItemClass.ENTITY : QT_TRANSLATE_NOOP("Constant", "Entity"), + nwItemClass.CUSTOM : QT_TRANSLATE_NOOP("Constant", "Custom"), + nwItemClass.ARCHIVE : QT_TRANSLATE_NOOP("Constant", "Outtakes"), + nwItemClass.TRASH : QT_TRANSLATE_NOOP("Constant", "Trash"), } CLASS_FLAG = { nwItemClass.NO_CLASS : "0", @@ -158,15 +159,15 @@ class nwLabels(): nwItemClass.TRASH : "cls_trash", } LAYOUT_NAME = { - nwItemLayout.NO_LAYOUT : "None", - nwItemLayout.TITLE : "Title Page", - nwItemLayout.BOOK : "Book", - nwItemLayout.PAGE : "Plain Page", - nwItemLayout.PARTITION : "Partition", - nwItemLayout.UNNUMBERED : "Unnumbered", - nwItemLayout.CHAPTER : "Chapter", - nwItemLayout.SCENE : "Scene", - nwItemLayout.NOTE : "Note", + nwItemLayout.NO_LAYOUT : QT_TRANSLATE_NOOP("Constant", "None"), + nwItemLayout.TITLE : QT_TRANSLATE_NOOP("Constant", "Title Page"), + nwItemLayout.BOOK : QT_TRANSLATE_NOOP("Constant", "Book"), + nwItemLayout.PAGE : QT_TRANSLATE_NOOP("Constant", "Plain Page"), + nwItemLayout.PARTITION : QT_TRANSLATE_NOOP("Constant", "Partition"), + nwItemLayout.UNNUMBERED : QT_TRANSLATE_NOOP("Constant", "Unnumbered"), + nwItemLayout.CHAPTER : QT_TRANSLATE_NOOP("Constant", "Chapter"), + nwItemLayout.SCENE : QT_TRANSLATE_NOOP("Constant", "Scene"), + nwItemLayout.NOTE : QT_TRANSLATE_NOOP("Constant", "Note"), } LAYOUT_FLAG = { nwItemLayout.NO_LAYOUT : "Xo", @@ -180,27 +181,27 @@ class nwLabels(): nwItemLayout.NOTE : "Nt", } KEY_NAME = { - nwKeyWords.TAG_KEY : "Tag", - nwKeyWords.POV_KEY : "Point of View", - nwKeyWords.FOCUS_KEY : "Focus", - nwKeyWords.CHAR_KEY : "Characters", - nwKeyWords.PLOT_KEY : "Plot", - nwKeyWords.TIME_KEY : "Timeline", - nwKeyWords.WORLD_KEY : "Locations", - nwKeyWords.OBJECT_KEY : "Objects", - nwKeyWords.ENTITY_KEY : "Entities", - nwKeyWords.CUSTOM_KEY : "Custom", + nwKeyWords.TAG_KEY : QT_TRANSLATE_NOOP("Constant", "Tag"), + nwKeyWords.POV_KEY : QT_TRANSLATE_NOOP("Constant", "Point of View"), + nwKeyWords.FOCUS_KEY : QT_TRANSLATE_NOOP("Constant", "Focus"), + nwKeyWords.CHAR_KEY : QT_TRANSLATE_NOOP("Constant", "Characters"), + nwKeyWords.PLOT_KEY : QT_TRANSLATE_NOOP("Constant", "Plot"), + nwKeyWords.TIME_KEY : QT_TRANSLATE_NOOP("Constant", "Timeline"), + nwKeyWords.WORLD_KEY : QT_TRANSLATE_NOOP("Constant", "Locations"), + nwKeyWords.OBJECT_KEY : QT_TRANSLATE_NOOP("Constant", "Objects"), + nwKeyWords.ENTITY_KEY : QT_TRANSLATE_NOOP("Constant", "Entities"), + nwKeyWords.CUSTOM_KEY : QT_TRANSLATE_NOOP("Constant", "Custom"), } OUTLINE_COLS = { - nwOutline.TITLE : "Title", - nwOutline.LEVEL : "Level", - nwOutline.LABEL : "Document", - nwOutline.LINE : "Line", - nwOutline.CCOUNT : "Chars", - nwOutline.WCOUNT : "Words", - nwOutline.PCOUNT : "Pars", - nwOutline.POV : "POV", - nwOutline.FOCUS : "Focus", + nwOutline.TITLE : QT_TRANSLATE_NOOP("Constant", "Title"), + nwOutline.LEVEL : QT_TRANSLATE_NOOP("Constant", "Level"), + nwOutline.LABEL : QT_TRANSLATE_NOOP("Constant", "Document"), + nwOutline.LINE : QT_TRANSLATE_NOOP("Constant", "Line"), + nwOutline.CCOUNT : QT_TRANSLATE_NOOP("Constant", "Chars"), + nwOutline.WCOUNT : QT_TRANSLATE_NOOP("Constant", "Words"), + nwOutline.PCOUNT : QT_TRANSLATE_NOOP("Constant", "Pars"), + nwOutline.POV : QT_TRANSLATE_NOOP("Constant", "POV"), + nwOutline.FOCUS : QT_TRANSLATE_NOOP("Constant", "Focus"), nwOutline.CHAR : KEY_NAME[nwKeyWords.CHAR_KEY], nwOutline.PLOT : KEY_NAME[nwKeyWords.PLOT_KEY], nwOutline.TIME : KEY_NAME[nwKeyWords.TIME_KEY], @@ -208,7 +209,7 @@ class nwLabels(): nwOutline.OBJECT : KEY_NAME[nwKeyWords.OBJECT_KEY], nwOutline.ENTITY : KEY_NAME[nwKeyWords.ENTITY_KEY], nwOutline.CUSTOM : KEY_NAME[nwKeyWords.CUSTOM_KEY], - nwOutline.SYNOP : "Synopsis", + nwOutline.SYNOP : QT_TRANSLATE_NOOP("Constant", "Synopsis"), } # END Class nwLabels @@ -218,28 +219,28 @@ class nwQuotes(): Source: https://en.wikipedia.org/wiki/Quotation_mark """ SYMBOLS = { - "\u0027" : "Straight single quotation mark", - "\u0022" : "Straight double quotation mark", + "\u0027" : QT_TRANSLATE_NOOP("Constant", "Straight single quotation mark"), + "\u0022" : QT_TRANSLATE_NOOP("Constant", "Straight double quotation mark"), - "\u2018" : "Left single quotation mark", - "\u2019" : "Right single quotation mark", - "\u201a" : "Single low-9 quotation mark", - "\u201b" : "Single high-reversed-9 quotation mark", - "\u201c" : "Left double quotation mark", - "\u201d" : "Right double quotation mark", - "\u201e" : "Double low-9 quotation mark", - "\u201f" : "Double high-reversed-9 quotation mark", - "\u2e42" : "Double low-reversed-9 quotation mark", + "\u2018" : QT_TRANSLATE_NOOP("Constant", "Left single quotation mark"), + "\u2019" : QT_TRANSLATE_NOOP("Constant", "Right single quotation mark"), + "\u201a" : QT_TRANSLATE_NOOP("Constant", "Single low-9 quotation mark"), + "\u201b" : QT_TRANSLATE_NOOP("Constant", "Single high-reversed-9 quotation mark"), + "\u201c" : QT_TRANSLATE_NOOP("Constant", "Left double quotation mark"), + "\u201d" : QT_TRANSLATE_NOOP("Constant", "Right double quotation mark"), + "\u201e" : QT_TRANSLATE_NOOP("Constant", "Double low-9 quotation mark"), + "\u201f" : QT_TRANSLATE_NOOP("Constant", "Double high-reversed-9 quotation mark"), + "\u2e42" : QT_TRANSLATE_NOOP("Constant", "Double low-reversed-9 quotation mark"), - "\u2039" : "Single left-pointing angle quotation mark", - "\u203a" : "Single right-pointing angle quotation mark", - "\u00ab" : "Left-pointing double angle quotation mark", - "\u00bb" : "Right-pointing double angle quotation mark", + "\u2039" : QT_TRANSLATE_NOOP("Constant", "Single left-pointing angle quotation mark"), + "\u203a" : QT_TRANSLATE_NOOP("Constant", "Single right-pointing angle quotation mark"), + "\u00ab" : QT_TRANSLATE_NOOP("Constant", "Left-pointing double angle quotation mark"), + "\u00bb" : QT_TRANSLATE_NOOP("Constant", "Right-pointing double angle quotation mark"), - "\u300c" : "Left corner bracket", - "\u300d" : "Right corner bracket", - "\u300e" : "Left white corner bracket", - "\u300f" : "Right white corner bracket", + "\u300c" : QT_TRANSLATE_NOOP("Constant", "Left corner bracket"), + "\u300d" : QT_TRANSLATE_NOOP("Constant", "Right corner bracket"), + "\u300e" : QT_TRANSLATE_NOOP("Constant", "Left white corner bracket"), + "\u300f" : QT_TRANSLATE_NOOP("Constant", "Right white corner bracket"), } # END Class nwQuotes diff --git a/nw/constants/iso.py b/nw/constants/iso.py index 94c6ef81..9d2c38aa 100644 --- a/nw/constants/iso.py +++ b/nw/constants/iso.py @@ -24,193 +24,196 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ +from PyQt5.QtCore import QT_TRANSLATE_NOOP + + class isoLanguage(): ISO_639_1 = { - "aa" : "Afar", - "ab" : "Abkhazian", - "ae" : "Avestan", - "af" : "Afrikaans", - "ak" : "Akan", - "am" : "Amharic", - "an" : "Aragonese", - "ar" : "Arabic", - "as" : "Assamese", - "av" : "Avaric", - "ay" : "Aymara", - "az" : "Azerbaijani", - "ba" : "Bashkir", - "be" : "Belarusian", - "bg" : "Bulgarian", - "bh" : "Bihari languages", - "bi" : "Bislama", - "bm" : "Bambara", - "bn" : "Bengali", - "bo" : "Tibetan", - "br" : "Breton", - "bs" : "Bosnian", - "ca" : "Catalan", - "ce" : "Chechen", - "ch" : "Chamorro", - "co" : "Corsican", - "cr" : "Cree", - "cs" : "Czech", - "cu" : "Church Slavic", - "cv" : "Chuvash", - "cy" : "Welsh", - "da" : "Danish", - "de" : "German", - "dv" : "Divehi", - "dz" : "Dzongkha", - "ee" : "Ewe", - "el" : "Modern Greek", - "en" : "English", - "eo" : "Esperanto", - "es" : "Spanish", - "et" : "Estonian", - "eu" : "Basque", - "fa" : "Persian", - "ff" : "Fulah", - "fi" : "Finnish", - "fj" : "Fijian", - "fo" : "Faroese", - "fr" : "French", - "fy" : "Western Frisian", - "ga" : "Irish", - "gd" : "Gaelic", - "gl" : "Galician", - "gn" : "Guarani", - "gu" : "Gujarati", - "gv" : "Manx", - "ha" : "Hausa", - "he" : "Hebrew", - "hi" : "Hindi", - "ho" : "Hiri Motu", - "hr" : "Croatian", - "ht" : "Haitian", - "hu" : "Hungarian", - "hy" : "Armenian", - "hz" : "Herero", - "ia" : "Interlingua", - "id" : "Indonesian", - "ie" : "Interlingue", - "ig" : "Igbo", - "ii" : "Sichuan Yi", - "ik" : "Inupiaq", - "io" : "Ido", - "is" : "Icelandic", - "it" : "Italian", - "iu" : "Inuktitut", - "ja" : "Japanese", - "jv" : "Javanese", - "ka" : "Georgian", - "kg" : "Kongo", - "ki" : "Kikuyu", - "kj" : "Kuanyama", - "kk" : "Kazakh", - "kl" : "Kalaallisut", - "km" : "Central Khmer", - "kn" : "Kannada", - "ko" : "Korean", - "kr" : "Kanuri", - "ks" : "Kashmiri", - "ku" : "Kurdish", - "kv" : "Komi", - "kw" : "Cornish", - "ky" : "Kirghiz", - "la" : "Latin", - "lb" : "Luxembourgish", - "lg" : "Ganda", - "li" : "Limburgan", - "ln" : "Lingala", - "lo" : "Lao", - "lt" : "Lithuanian", - "lu" : "Luba-Katanga", - "lv" : "Latvian", - "mg" : "Malagasy", - "mh" : "Marshallese", - "mi" : "Maori", - "mk" : "Macedonian", - "ml" : "Malayalam", - "mn" : "Mongolian", - "mr" : "Marathi", - "ms" : "Malay", - "mt" : "Maltese", - "my" : "Burmese", - "na" : "Nauru", - "nb" : "Norwegian Bokmål", - "nd" : "North Ndebele", - "ne" : "Nepali", - "ng" : "Ndonga", - "nl" : "Dutch", - "nn" : "Norwegian Nynorsk", - "no" : "Norwegian", - "nr" : "South Ndebele", - "nv" : "Navajo", - "ny" : "Chichewa", - "oc" : "Occitan", - "oj" : "Ojibwa", - "om" : "Oromo", - "or" : "Oriya", - "os" : "Ossetian", - "pa" : "Panjabi", - "pi" : "Pali", - "pl" : "Polish", - "ps" : "Pushto", - "pt" : "Portuguese", - "qu" : "Quechua", - "rm" : "Romansh", - "rn" : "Rundi", - "ro" : "Romanian", - "ru" : "Russian", - "rw" : "Kinyarwanda", - "sa" : "Sanskrit", - "sc" : "Sardinian", - "sd" : "Sindhi", - "se" : "Northern Sami", - "sg" : "Sango", - "si" : "Sinhala", - "sk" : "Slovak", - "sl" : "Slovenian", - "sm" : "Samoan", - "sn" : "Shona", - "so" : "Somali", - "sq" : "Albanian", - "sr" : "Serbian", - "ss" : "Swati", - "st" : "Southern Sotho", - "su" : "Sundanese", - "sv" : "Swedish", - "sw" : "Swahili", - "ta" : "Tamil", - "te" : "Telugu", - "tg" : "Tajik", - "th" : "Thai", - "ti" : "Tigrinya", - "tk" : "Turkmen", - "tl" : "Tagalog", - "tn" : "Tswana", - "to" : "Tonga", - "tr" : "Turkish", - "ts" : "Tsonga", - "tt" : "Tatar", - "tw" : "Twi", - "ty" : "Tahitian", - "ug" : "Uighur", - "uk" : "Ukrainian", - "ur" : "Urdu", - "uz" : "Uzbek", - "ve" : "Venda", - "vi" : "Vietnamese", - "vo" : "Volapük", - "wa" : "Walloon", - "wo" : "Wolof", - "xh" : "Xhosa", - "yi" : "Yiddish", - "yo" : "Yoruba", - "za" : "Zhuang", - "zh" : "Chinese", - "zu" : "Zulu", + "aa" : QT_TRANSLATE_NOOP("ISO", "Afar"), + "ab" : QT_TRANSLATE_NOOP("ISO", "Abkhazian"), + "ae" : QT_TRANSLATE_NOOP("ISO", "Avestan"), + "af" : QT_TRANSLATE_NOOP("ISO", "Afrikaans"), + "ak" : QT_TRANSLATE_NOOP("ISO", "Akan"), + "am" : QT_TRANSLATE_NOOP("ISO", "Amharic"), + "an" : QT_TRANSLATE_NOOP("ISO", "Aragonese"), + "ar" : QT_TRANSLATE_NOOP("ISO", "Arabic"), + "as" : QT_TRANSLATE_NOOP("ISO", "Assamese"), + "av" : QT_TRANSLATE_NOOP("ISO", "Avaric"), + "ay" : QT_TRANSLATE_NOOP("ISO", "Aymara"), + "az" : QT_TRANSLATE_NOOP("ISO", "Azerbaijani"), + "ba" : QT_TRANSLATE_NOOP("ISO", "Bashkir"), + "be" : QT_TRANSLATE_NOOP("ISO", "Belarusian"), + "bg" : QT_TRANSLATE_NOOP("ISO", "Bulgarian"), + "bh" : QT_TRANSLATE_NOOP("ISO", "Bihari languages"), + "bi" : QT_TRANSLATE_NOOP("ISO", "Bislama"), + "bm" : QT_TRANSLATE_NOOP("ISO", "Bambara"), + "bn" : QT_TRANSLATE_NOOP("ISO", "Bengali"), + "bo" : QT_TRANSLATE_NOOP("ISO", "Tibetan"), + "br" : QT_TRANSLATE_NOOP("ISO", "Breton"), + "bs" : QT_TRANSLATE_NOOP("ISO", "Bosnian"), + "ca" : QT_TRANSLATE_NOOP("ISO", "Catalan"), + "ce" : QT_TRANSLATE_NOOP("ISO", "Chechen"), + "ch" : QT_TRANSLATE_NOOP("ISO", "Chamorro"), + "co" : QT_TRANSLATE_NOOP("ISO", "Corsican"), + "cr" : QT_TRANSLATE_NOOP("ISO", "Cree"), + "cs" : QT_TRANSLATE_NOOP("ISO", "Czech"), + "cu" : QT_TRANSLATE_NOOP("ISO", "Church Slavic"), + "cv" : QT_TRANSLATE_NOOP("ISO", "Chuvash"), + "cy" : QT_TRANSLATE_NOOP("ISO", "Welsh"), + "da" : QT_TRANSLATE_NOOP("ISO", "Danish"), + "de" : QT_TRANSLATE_NOOP("ISO", "German"), + "dv" : QT_TRANSLATE_NOOP("ISO", "Divehi"), + "dz" : QT_TRANSLATE_NOOP("ISO", "Dzongkha"), + "ee" : QT_TRANSLATE_NOOP("ISO", "Ewe"), + "el" : QT_TRANSLATE_NOOP("ISO", "Modern Greek"), + "en" : QT_TRANSLATE_NOOP("ISO", "English"), + "eo" : QT_TRANSLATE_NOOP("ISO", "Esperanto"), + "es" : QT_TRANSLATE_NOOP("ISO", "Spanish"), + "et" : QT_TRANSLATE_NOOP("ISO", "Estonian"), + "eu" : QT_TRANSLATE_NOOP("ISO", "Basque"), + "fa" : QT_TRANSLATE_NOOP("ISO", "Persian"), + "ff" : QT_TRANSLATE_NOOP("ISO", "Fulah"), + "fi" : QT_TRANSLATE_NOOP("ISO", "Finnish"), + "fj" : QT_TRANSLATE_NOOP("ISO", "Fijian"), + "fo" : QT_TRANSLATE_NOOP("ISO", "Faroese"), + "fr" : QT_TRANSLATE_NOOP("ISO", "French"), + "fy" : QT_TRANSLATE_NOOP("ISO", "Western Frisian"), + "ga" : QT_TRANSLATE_NOOP("ISO", "Irish"), + "gd" : QT_TRANSLATE_NOOP("ISO", "Gaelic"), + "gl" : QT_TRANSLATE_NOOP("ISO", "Galician"), + "gn" : QT_TRANSLATE_NOOP("ISO", "Guarani"), + "gu" : QT_TRANSLATE_NOOP("ISO", "Gujarati"), + "gv" : QT_TRANSLATE_NOOP("ISO", "Manx"), + "ha" : QT_TRANSLATE_NOOP("ISO", "Hausa"), + "he" : QT_TRANSLATE_NOOP("ISO", "Hebrew"), + "hi" : QT_TRANSLATE_NOOP("ISO", "Hindi"), + "ho" : QT_TRANSLATE_NOOP("ISO", "Hiri Motu"), + "hr" : QT_TRANSLATE_NOOP("ISO", "Croatian"), + "ht" : QT_TRANSLATE_NOOP("ISO", "Haitian"), + "hu" : QT_TRANSLATE_NOOP("ISO", "Hungarian"), + "hy" : QT_TRANSLATE_NOOP("ISO", "Armenian"), + "hz" : QT_TRANSLATE_NOOP("ISO", "Herero"), + "ia" : QT_TRANSLATE_NOOP("ISO", "Interlingua"), + "id" : QT_TRANSLATE_NOOP("ISO", "Indonesian"), + "ie" : QT_TRANSLATE_NOOP("ISO", "Interlingue"), + "ig" : QT_TRANSLATE_NOOP("ISO", "Igbo"), + "ii" : QT_TRANSLATE_NOOP("ISO", "Sichuan Yi"), + "ik" : QT_TRANSLATE_NOOP("ISO", "Inupiaq"), + "io" : QT_TRANSLATE_NOOP("ISO", "Ido"), + "is" : QT_TRANSLATE_NOOP("ISO", "Icelandic"), + "it" : QT_TRANSLATE_NOOP("ISO", "Italian"), + "iu" : QT_TRANSLATE_NOOP("ISO", "Inuktitut"), + "ja" : QT_TRANSLATE_NOOP("ISO", "Japanese"), + "jv" : QT_TRANSLATE_NOOP("ISO", "Javanese"), + "ka" : QT_TRANSLATE_NOOP("ISO", "Georgian"), + "kg" : QT_TRANSLATE_NOOP("ISO", "Kongo"), + "ki" : QT_TRANSLATE_NOOP("ISO", "Kikuyu"), + "kj" : QT_TRANSLATE_NOOP("ISO", "Kuanyama"), + "kk" : QT_TRANSLATE_NOOP("ISO", "Kazakh"), + "kl" : QT_TRANSLATE_NOOP("ISO", "Kalaallisut"), + "km" : QT_TRANSLATE_NOOP("ISO", "Central Khmer"), + "kn" : QT_TRANSLATE_NOOP("ISO", "Kannada"), + "ko" : QT_TRANSLATE_NOOP("ISO", "Korean"), + "kr" : QT_TRANSLATE_NOOP("ISO", "Kanuri"), + "ks" : QT_TRANSLATE_NOOP("ISO", "Kashmiri"), + "ku" : QT_TRANSLATE_NOOP("ISO", "Kurdish"), + "kv" : QT_TRANSLATE_NOOP("ISO", "Komi"), + "kw" : QT_TRANSLATE_NOOP("ISO", "Cornish"), + "ky" : QT_TRANSLATE_NOOP("ISO", "Kirghiz"), + "la" : QT_TRANSLATE_NOOP("ISO", "Latin"), + "lb" : QT_TRANSLATE_NOOP("ISO", "Luxembourgish"), + "lg" : QT_TRANSLATE_NOOP("ISO", "Ganda"), + "li" : QT_TRANSLATE_NOOP("ISO", "Limburgan"), + "ln" : QT_TRANSLATE_NOOP("ISO", "Lingala"), + "lo" : QT_TRANSLATE_NOOP("ISO", "Lao"), + "lt" : QT_TRANSLATE_NOOP("ISO", "Lithuanian"), + "lu" : QT_TRANSLATE_NOOP("ISO", "Luba-Katanga"), + "lv" : QT_TRANSLATE_NOOP("ISO", "Latvian"), + "mg" : QT_TRANSLATE_NOOP("ISO", "Malagasy"), + "mh" : QT_TRANSLATE_NOOP("ISO", "Marshallese"), + "mi" : QT_TRANSLATE_NOOP("ISO", "Maori"), + "mk" : QT_TRANSLATE_NOOP("ISO", "Macedonian"), + "ml" : QT_TRANSLATE_NOOP("ISO", "Malayalam"), + "mn" : QT_TRANSLATE_NOOP("ISO", "Mongolian"), + "mr" : QT_TRANSLATE_NOOP("ISO", "Marathi"), + "ms" : QT_TRANSLATE_NOOP("ISO", "Malay"), + "mt" : QT_TRANSLATE_NOOP("ISO", "Maltese"), + "my" : QT_TRANSLATE_NOOP("ISO", "Burmese"), + "na" : QT_TRANSLATE_NOOP("ISO", "Nauru"), + "nb" : QT_TRANSLATE_NOOP("ISO", "Norwegian Bokm\u0229l"), + "nd" : QT_TRANSLATE_NOOP("ISO", "North Ndebele"), + "ne" : QT_TRANSLATE_NOOP("ISO", "Nepali"), + "ng" : QT_TRANSLATE_NOOP("ISO", "Ndonga"), + "nl" : QT_TRANSLATE_NOOP("ISO", "Dutch"), + "nn" : QT_TRANSLATE_NOOP("ISO", "Norwegian Nynorsk"), + "no" : QT_TRANSLATE_NOOP("ISO", "Norwegian"), + "nr" : QT_TRANSLATE_NOOP("ISO", "South Ndebele"), + "nv" : QT_TRANSLATE_NOOP("ISO", "Navajo"), + "ny" : QT_TRANSLATE_NOOP("ISO", "Chichewa"), + "oc" : QT_TRANSLATE_NOOP("ISO", "Occitan"), + "oj" : QT_TRANSLATE_NOOP("ISO", "Ojibwa"), + "om" : QT_TRANSLATE_NOOP("ISO", "Oromo"), + "or" : QT_TRANSLATE_NOOP("ISO", "Oriya"), + "os" : QT_TRANSLATE_NOOP("ISO", "Ossetian"), + "pa" : QT_TRANSLATE_NOOP("ISO", "Panjabi"), + "pi" : QT_TRANSLATE_NOOP("ISO", "Pali"), + "pl" : QT_TRANSLATE_NOOP("ISO", "Polish"), + "ps" : QT_TRANSLATE_NOOP("ISO", "Pushto"), + "pt" : QT_TRANSLATE_NOOP("ISO", "Portuguese"), + "qu" : QT_TRANSLATE_NOOP("ISO", "Quechua"), + "rm" : QT_TRANSLATE_NOOP("ISO", "Romansh"), + "rn" : QT_TRANSLATE_NOOP("ISO", "Rundi"), + "ro" : QT_TRANSLATE_NOOP("ISO", "Romanian"), + "ru" : QT_TRANSLATE_NOOP("ISO", "Russian"), + "rw" : QT_TRANSLATE_NOOP("ISO", "Kinyarwanda"), + "sa" : QT_TRANSLATE_NOOP("ISO", "Sanskrit"), + "sc" : QT_TRANSLATE_NOOP("ISO", "Sardinian"), + "sd" : QT_TRANSLATE_NOOP("ISO", "Sindhi"), + "se" : QT_TRANSLATE_NOOP("ISO", "Northern Sami"), + "sg" : QT_TRANSLATE_NOOP("ISO", "Sango"), + "si" : QT_TRANSLATE_NOOP("ISO", "Sinhala"), + "sk" : QT_TRANSLATE_NOOP("ISO", "Slovak"), + "sl" : QT_TRANSLATE_NOOP("ISO", "Slovenian"), + "sm" : QT_TRANSLATE_NOOP("ISO", "Samoan"), + "sn" : QT_TRANSLATE_NOOP("ISO", "Shona"), + "so" : QT_TRANSLATE_NOOP("ISO", "Somali"), + "sq" : QT_TRANSLATE_NOOP("ISO", "Albanian"), + "sr" : QT_TRANSLATE_NOOP("ISO", "Serbian"), + "ss" : QT_TRANSLATE_NOOP("ISO", "Swati"), + "st" : QT_TRANSLATE_NOOP("ISO", "Southern Sotho"), + "su" : QT_TRANSLATE_NOOP("ISO", "Sundanese"), + "sv" : QT_TRANSLATE_NOOP("ISO", "Swedish"), + "sw" : QT_TRANSLATE_NOOP("ISO", "Swahili"), + "ta" : QT_TRANSLATE_NOOP("ISO", "Tamil"), + "te" : QT_TRANSLATE_NOOP("ISO", "Telugu"), + "tg" : QT_TRANSLATE_NOOP("ISO", "Tajik"), + "th" : QT_TRANSLATE_NOOP("ISO", "Thai"), + "ti" : QT_TRANSLATE_NOOP("ISO", "Tigrinya"), + "tk" : QT_TRANSLATE_NOOP("ISO", "Turkmen"), + "tl" : QT_TRANSLATE_NOOP("ISO", "Tagalog"), + "tn" : QT_TRANSLATE_NOOP("ISO", "Tswana"), + "to" : QT_TRANSLATE_NOOP("ISO", "Tonga"), + "tr" : QT_TRANSLATE_NOOP("ISO", "Turkish"), + "ts" : QT_TRANSLATE_NOOP("ISO", "Tsonga"), + "tt" : QT_TRANSLATE_NOOP("ISO", "Tatar"), + "tw" : QT_TRANSLATE_NOOP("ISO", "Twi"), + "ty" : QT_TRANSLATE_NOOP("ISO", "Tahitian"), + "ug" : QT_TRANSLATE_NOOP("ISO", "Uighur"), + "uk" : QT_TRANSLATE_NOOP("ISO", "Ukrainian"), + "ur" : QT_TRANSLATE_NOOP("ISO", "Urdu"), + "uz" : QT_TRANSLATE_NOOP("ISO", "Uzbek"), + "ve" : QT_TRANSLATE_NOOP("ISO", "Venda"), + "vi" : QT_TRANSLATE_NOOP("ISO", "Vietnamese"), + "vo" : QT_TRANSLATE_NOOP("ISO", "Volap\u00fck"), + "wa" : QT_TRANSLATE_NOOP("ISO", "Walloon"), + "wo" : QT_TRANSLATE_NOOP("ISO", "Wolof"), + "xh" : QT_TRANSLATE_NOOP("ISO", "Xhosa"), + "yi" : QT_TRANSLATE_NOOP("ISO", "Yiddish"), + "yo" : QT_TRANSLATE_NOOP("ISO", "Yoruba"), + "za" : QT_TRANSLATE_NOOP("ISO", "Zhuang"), + "zh" : QT_TRANSLATE_NOOP("ISO", "Chinese"), + "zu" : QT_TRANSLATE_NOOP("ISO", "Zulu"), } # END Class isoLanguage @@ -218,255 +221,255 @@ class isoLanguage(): class isoCountry(): ISO_3166_1_alpha_2 = { - "AD" : "Andorra", - "AE" : "United Arab Emirates", - "AF" : "Afghanistan", - "AG" : "Antigua and Barbuda", - "AI" : "Anguilla", - "AL" : "Albania", - "AM" : "Armenia", - "AO" : "Angola", - "AQ" : "Antarctica", - "AR" : "Argentina", - "AS" : "American Samoa", - "AT" : "Austria", - "AU" : "Australia", - "AW" : "Aruba", - "AX" : "Åland Islands", - "AZ" : "Azerbaijan", - "BA" : "Bosnia and Herzegovina", - "BB" : "Barbados", - "BD" : "Bangladesh", - "BE" : "Belgium", - "BF" : "Burkina Faso", - "BG" : "Bulgaria", - "BH" : "Bahrain", - "BI" : "Burundi", - "BJ" : "Benin", - "BL" : "Saint Barthélemy", - "BM" : "Bermuda", - "BN" : "Brunei Darussalam", - "BO" : "Plurinational State of Bolivia", - "BQ" : "Sint Eustatius and Saba Bonaire", - "BR" : "Brazil", - "BS" : "Bahamas", - "BT" : "Bhutan", - "BV" : "Bouvet Island", - "BW" : "Botswana", - "BY" : "Belarus", - "BZ" : "Belize", - "CA" : "Canada", - "CC" : "Cocos (Keeling) Islands", - "CD" : "The Democratic Republic of the Congo", - "CF" : "Central African Republic", - "CG" : "Congo", - "CH" : "Switzerland", - "CI" : "Côte d'Ivoire", - "CK" : "Cook Islands", - "CL" : "Chile", - "CM" : "Cameroon", - "CN" : "China", - "CO" : "Colombia", - "CR" : "Costa Rica", - "CU" : "Cuba", - "CV" : "Cape Verde", - "CW" : "Curaçao", - "CX" : "Christmas Island", - "CY" : "Cyprus", - "CZ" : "Czech Republic", - "DE" : "Germany", - "DJ" : "Djibouti", - "DK" : "Denmark", - "DM" : "Dominica", - "DO" : "Dominican Republic", - "DZ" : "Algeria", - "EC" : "Ecuador", - "EE" : "Estonia", - "EG" : "Egypt", - "EH" : "Western Sahara", - "ER" : "Eritrea", - "ES" : "Spain", - "ET" : "Ethiopia", - "FI" : "Finland", - "FJ" : "Fiji", - "FK" : "Falkland Islands (Malvinas)", - "FM" : "Federated States of Micronesia", - "FO" : "Faroe Islands", - "FR" : "France", - "GA" : "Gabon", - "GB" : "United Kingdom", - "GD" : "Grenada", - "GE" : "Georgia", - "GF" : "French Guiana", - "GG" : "Guernsey", - "GH" : "Ghana", - "GI" : "Gibraltar", - "GL" : "Greenland", - "GM" : "Gambia", - "GN" : "Guinea", - "GP" : "Guadeloupe", - "GQ" : "Equatorial Guinea", - "GR" : "Greece", - "GS" : "South Georgia and the South Sandwich Islands", - "GT" : "Guatemala", - "GU" : "Guam", - "GW" : "Guinea-Bissau", - "GY" : "Guyana", - "HK" : "Hong Kong", - "HM" : "Heard Island and McDonald Islands", - "HN" : "Honduras", - "HR" : "Croatia", - "HT" : "Haiti", - "HU" : "Hungary", - "ID" : "Indonesia", - "IE" : "Ireland", - "IL" : "Israel", - "IM" : "Isle of Man", - "IN" : "India", - "IO" : "British Indian Ocean Territory", - "IQ" : "Iraq", - "IR" : "Islamic Republic of Iran", - "IS" : "Iceland", - "IT" : "Italy", - "JE" : "Jersey", - "JM" : "Jamaica", - "JO" : "Jordan", - "JP" : "Japan", - "KE" : "Kenya", - "KG" : "Kyrgyzstan", - "KH" : "Cambodia", - "KI" : "Kiribati", - "KM" : "Comoros", - "KN" : "Saint Kitts and Nevis", - "KP" : "Democratic People's Republic of Korea", - "KR" : "Republic of Korea", - "KW" : "Kuwait", - "KY" : "Cayman Islands", - "KZ" : "Kazakhstan", - "LA" : "Lao People's Democratic Republic", - "LB" : "Lebanon", - "LC" : "Saint Lucia", - "LI" : "Liechtenstein", - "LK" : "Sri Lanka", - "LR" : "Liberia", - "LS" : "Lesotho", - "LT" : "Lithuania", - "LU" : "Luxembourg", - "LV" : "Latvia", - "LY" : "Libya", - "MA" : "Morocco", - "MC" : "Monaco", - "MD" : "Republic of Moldova", - "ME" : "Montenegro", - "MF" : "Saint Martin (French part)", - "MG" : "Madagascar", - "MH" : "Marshall Islands", - "MK" : "The Former Yugoslav Republic of Macedonia", - "ML" : "Mali", - "MM" : "Myanmar", - "MN" : "Mongolia", - "MO" : "Macao", - "MP" : "Northern Mariana Islands", - "MQ" : "Martinique", - "MR" : "Mauritania", - "MS" : "Montserrat", - "MT" : "Malta", - "MU" : "Mauritius", - "MV" : "Maldives", - "MW" : "Malawi", - "MX" : "Mexico", - "MY" : "Malaysia", - "MZ" : "Mozambique", - "NA" : "Namibia", - "NC" : "New Caledonia", - "NE" : "Niger", - "NF" : "Norfolk Island", - "NG" : "Nigeria", - "NI" : "Nicaragua", - "NL" : "Netherlands", - "NO" : "Norway", - "NP" : "Nepal", - "NR" : "Nauru", - "NU" : "Niue", - "NZ" : "New Zealand", - "OM" : "Oman", - "PA" : "Panama", - "PE" : "Peru", - "PF" : "French Polynesia", - "PG" : "Papua New Guinea", - "PH" : "Philippines", - "PK" : "Pakistan", - "PL" : "Poland", - "PM" : "Saint Pierre and Miquelon", - "PN" : "Pitcairn", - "PR" : "Puerto Rico", - "PS" : "State of Palestine", - "PT" : "Portugal", - "PW" : "Palau", - "PY" : "Paraguay", - "QA" : "Qatar", - "RE" : "Réunion", - "RO" : "Romania", - "RS" : "Serbia", - "RU" : "Russian Federation", - "RW" : "Rwanda", - "SA" : "Saudi Arabia", - "SB" : "Solomon Islands", - "SC" : "Seychelles", - "SD" : "Sudan", - "SE" : "Sweden", - "SG" : "Singapore", - "SH" : "Saint Helena, Ascension and Tristan da Cunha", - "SI" : "Slovenia", - "SJ" : "Svalbard and Jan Mayen", - "SK" : "Slovakia", - "SL" : "Sierra Leone", - "SM" : "San Marino", - "SN" : "Senegal", - "SO" : "Somalia", - "SR" : "Suriname", - "SS" : "South Sudan", - "ST" : "Sao Tome and Principe", - "SV" : "El Salvador", - "SX" : "Sint Maarten", - "SY" : "Syrian Arab Republic", - "SZ" : "Swaziland", - "TC" : "Turks and Caicos Islands", - "TD" : "Chad", - "TF" : "French Southern Territories", - "TG" : "Togo", - "TH" : "Thailand", - "TJ" : "Tajikistan", - "TK" : "Tokelau", - "TL" : "Timor-Leste", - "TM" : "Turkmenistan", - "TN" : "Tunisia", - "TO" : "Tonga", - "TR" : "Turkey", - "TT" : "Trinidad and Tobago", - "TV" : "Tuvalu", - "TW" : "Taiwan, Province of China", - "TZ" : "United Republic of Tanzania", - "UA" : "Ukraine", - "UG" : "Uganda", - "UM" : "United States Minor Outlying Islands", - "US" : "United States", - "UY" : "Uruguay", - "UZ" : "Uzbekistan", - "VA" : "Holy See (Vatican City State)", - "VC" : "Saint Vincent and the Grenadines", - "VE" : "Bolivarian Republic of Venezuela", - "VG" : "British Virgin Islands", - "VI" : "U.S. Virgin Islands", - "VN" : "Viet Nam", - "VU" : "Vanuatu", - "WF" : "Wallis and Futuna", - "WS" : "Samoa", - "YE" : "Yemen", - "YT" : "Mayotte", - "ZA" : "South Africa", - "ZM" : "Zambia", - "ZW" : "Zimbabwe", + "AD" : QT_TRANSLATE_NOOP("ISO", "Andorra"), + "AE" : QT_TRANSLATE_NOOP("ISO", "United Arab Emirates"), + "AF" : QT_TRANSLATE_NOOP("ISO", "Afghanistan"), + "AG" : QT_TRANSLATE_NOOP("ISO", "Antigua and Barbuda"), + "AI" : QT_TRANSLATE_NOOP("ISO", "Anguilla"), + "AL" : QT_TRANSLATE_NOOP("ISO", "Albania"), + "AM" : QT_TRANSLATE_NOOP("ISO", "Armenia"), + "AO" : QT_TRANSLATE_NOOP("ISO", "Angola"), + "AQ" : QT_TRANSLATE_NOOP("ISO", "Antarctica"), + "AR" : QT_TRANSLATE_NOOP("ISO", "Argentina"), + "AS" : QT_TRANSLATE_NOOP("ISO", "American Samoa"), + "AT" : QT_TRANSLATE_NOOP("ISO", "Austria"), + "AU" : QT_TRANSLATE_NOOP("ISO", "Australia"), + "AW" : QT_TRANSLATE_NOOP("ISO", "Aruba"), + "AX" : QT_TRANSLATE_NOOP("ISO", "\u0197land Islands"), + "AZ" : QT_TRANSLATE_NOOP("ISO", "Azerbaijan"), + "BA" : QT_TRANSLATE_NOOP("ISO", "Bosnia and Herzegovina"), + "BB" : QT_TRANSLATE_NOOP("ISO", "Barbados"), + "BD" : QT_TRANSLATE_NOOP("ISO", "Bangladesh"), + "BE" : QT_TRANSLATE_NOOP("ISO", "Belgium"), + "BF" : QT_TRANSLATE_NOOP("ISO", "Burkina Faso"), + "BG" : QT_TRANSLATE_NOOP("ISO", "Bulgaria"), + "BH" : QT_TRANSLATE_NOOP("ISO", "Bahrain"), + "BI" : QT_TRANSLATE_NOOP("ISO", "Burundi"), + "BJ" : QT_TRANSLATE_NOOP("ISO", "Benin"), + "BL" : QT_TRANSLATE_NOOP("ISO", "Saint Barth\u00e9lemy"), + "BM" : QT_TRANSLATE_NOOP("ISO", "Bermuda"), + "BN" : QT_TRANSLATE_NOOP("ISO", "Brunei Darussalam"), + "BO" : QT_TRANSLATE_NOOP("ISO", "Plurinational State of Bolivia"), + "BQ" : QT_TRANSLATE_NOOP("ISO", "Sint Eustatius and Saba Bonaire"), + "BR" : QT_TRANSLATE_NOOP("ISO", "Brazil"), + "BS" : QT_TRANSLATE_NOOP("ISO", "Bahamas"), + "BT" : QT_TRANSLATE_NOOP("ISO", "Bhutan"), + "BV" : QT_TRANSLATE_NOOP("ISO", "Bouvet Island"), + "BW" : QT_TRANSLATE_NOOP("ISO", "Botswana"), + "BY" : QT_TRANSLATE_NOOP("ISO", "Belarus"), + "BZ" : QT_TRANSLATE_NOOP("ISO", "Belize"), + "CA" : QT_TRANSLATE_NOOP("ISO", "Canada"), + "CC" : QT_TRANSLATE_NOOP("ISO", "Cocos (Keeling) Islands"), + "CD" : QT_TRANSLATE_NOOP("ISO", "The Democratic Republic of the Congo"), + "CF" : QT_TRANSLATE_NOOP("ISO", "Central African Republic"), + "CG" : QT_TRANSLATE_NOOP("ISO", "Congo"), + "CH" : QT_TRANSLATE_NOOP("ISO", "Switzerland"), + "CI" : QT_TRANSLATE_NOOP("ISO", "C\u00f4te d'Ivoire"), + "CK" : QT_TRANSLATE_NOOP("ISO", "Cook Islands"), + "CL" : QT_TRANSLATE_NOOP("ISO", "Chile"), + "CM" : QT_TRANSLATE_NOOP("ISO", "Cameroon"), + "CN" : QT_TRANSLATE_NOOP("ISO", "China"), + "CO" : QT_TRANSLATE_NOOP("ISO", "Colombia"), + "CR" : QT_TRANSLATE_NOOP("ISO", "Costa Rica"), + "CU" : QT_TRANSLATE_NOOP("ISO", "Cuba"), + "CV" : QT_TRANSLATE_NOOP("ISO", "Cape Verde"), + "CW" : QT_TRANSLATE_NOOP("ISO", "Cura\u00e7ao"), + "CX" : QT_TRANSLATE_NOOP("ISO", "Christmas Island"), + "CY" : QT_TRANSLATE_NOOP("ISO", "Cyprus"), + "CZ" : QT_TRANSLATE_NOOP("ISO", "Czech Republic"), + "DE" : QT_TRANSLATE_NOOP("ISO", "Germany"), + "DJ" : QT_TRANSLATE_NOOP("ISO", "Djibouti"), + "DK" : QT_TRANSLATE_NOOP("ISO", "Denmark"), + "DM" : QT_TRANSLATE_NOOP("ISO", "Dominica"), + "DO" : QT_TRANSLATE_NOOP("ISO", "Dominican Republic"), + "DZ" : QT_TRANSLATE_NOOP("ISO", "Algeria"), + "EC" : QT_TRANSLATE_NOOP("ISO", "Ecuador"), + "EE" : QT_TRANSLATE_NOOP("ISO", "Estonia"), + "EG" : QT_TRANSLATE_NOOP("ISO", "Egypt"), + "EH" : QT_TRANSLATE_NOOP("ISO", "Western Sahara"), + "ER" : QT_TRANSLATE_NOOP("ISO", "Eritrea"), + "ES" : QT_TRANSLATE_NOOP("ISO", "Spain"), + "ET" : QT_TRANSLATE_NOOP("ISO", "Ethiopia"), + "FI" : QT_TRANSLATE_NOOP("ISO", "Finland"), + "FJ" : QT_TRANSLATE_NOOP("ISO", "Fiji"), + "FK" : QT_TRANSLATE_NOOP("ISO", "Falkland Islands (Malvinas)"), + "FM" : QT_TRANSLATE_NOOP("ISO", "Federated States of Micronesia"), + "FO" : QT_TRANSLATE_NOOP("ISO", "Faroe Islands"), + "FR" : QT_TRANSLATE_NOOP("ISO", "France"), + "GA" : QT_TRANSLATE_NOOP("ISO", "Gabon"), + "GB" : QT_TRANSLATE_NOOP("ISO", "United Kingdom"), + "GD" : QT_TRANSLATE_NOOP("ISO", "Grenada"), + "GE" : QT_TRANSLATE_NOOP("ISO", "Georgia"), + "GF" : QT_TRANSLATE_NOOP("ISO", "French Guiana"), + "GG" : QT_TRANSLATE_NOOP("ISO", "Guernsey"), + "GH" : QT_TRANSLATE_NOOP("ISO", "Ghana"), + "GI" : QT_TRANSLATE_NOOP("ISO", "Gibraltar"), + "GL" : QT_TRANSLATE_NOOP("ISO", "Greenland"), + "GM" : QT_TRANSLATE_NOOP("ISO", "Gambia"), + "GN" : QT_TRANSLATE_NOOP("ISO", "Guinea"), + "GP" : QT_TRANSLATE_NOOP("ISO", "Guadeloupe"), + "GQ" : QT_TRANSLATE_NOOP("ISO", "Equatorial Guinea"), + "GR" : QT_TRANSLATE_NOOP("ISO", "Greece"), + "GS" : QT_TRANSLATE_NOOP("ISO", "South Georgia and the South Sandwich Islands"), + "GT" : QT_TRANSLATE_NOOP("ISO", "Guatemala"), + "GU" : QT_TRANSLATE_NOOP("ISO", "Guam"), + "GW" : QT_TRANSLATE_NOOP("ISO", "Guinea-Bissau"), + "GY" : QT_TRANSLATE_NOOP("ISO", "Guyana"), + "HK" : QT_TRANSLATE_NOOP("ISO", "Hong Kong"), + "HM" : QT_TRANSLATE_NOOP("ISO", "Heard Island and McDonald Islands"), + "HN" : QT_TRANSLATE_NOOP("ISO", "Honduras"), + "HR" : QT_TRANSLATE_NOOP("ISO", "Croatia"), + "HT" : QT_TRANSLATE_NOOP("ISO", "Haiti"), + "HU" : QT_TRANSLATE_NOOP("ISO", "Hungary"), + "ID" : QT_TRANSLATE_NOOP("ISO", "Indonesia"), + "IE" : QT_TRANSLATE_NOOP("ISO", "Ireland"), + "IL" : QT_TRANSLATE_NOOP("ISO", "Israel"), + "IM" : QT_TRANSLATE_NOOP("ISO", "Isle of Man"), + "IN" : QT_TRANSLATE_NOOP("ISO", "India"), + "IO" : QT_TRANSLATE_NOOP("ISO", "British Indian Ocean Territory"), + "IQ" : QT_TRANSLATE_NOOP("ISO", "Iraq"), + "IR" : QT_TRANSLATE_NOOP("ISO", "Islamic Republic of Iran"), + "IS" : QT_TRANSLATE_NOOP("ISO", "Iceland"), + "IT" : QT_TRANSLATE_NOOP("ISO", "Italy"), + "JE" : QT_TRANSLATE_NOOP("ISO", "Jersey"), + "JM" : QT_TRANSLATE_NOOP("ISO", "Jamaica"), + "JO" : QT_TRANSLATE_NOOP("ISO", "Jordan"), + "JP" : QT_TRANSLATE_NOOP("ISO", "Japan"), + "KE" : QT_TRANSLATE_NOOP("ISO", "Kenya"), + "KG" : QT_TRANSLATE_NOOP("ISO", "Kyrgyzstan"), + "KH" : QT_TRANSLATE_NOOP("ISO", "Cambodia"), + "KI" : QT_TRANSLATE_NOOP("ISO", "Kiribati"), + "KM" : QT_TRANSLATE_NOOP("ISO", "Comoros"), + "KN" : QT_TRANSLATE_NOOP("ISO", "Saint Kitts and Nevis"), + "KP" : QT_TRANSLATE_NOOP("ISO", "Democratic People's Republic of Korea"), + "KR" : QT_TRANSLATE_NOOP("ISO", "Republic of Korea"), + "KW" : QT_TRANSLATE_NOOP("ISO", "Kuwait"), + "KY" : QT_TRANSLATE_NOOP("ISO", "Cayman Islands"), + "KZ" : QT_TRANSLATE_NOOP("ISO", "Kazakhstan"), + "LA" : QT_TRANSLATE_NOOP("ISO", "Lao People's Democratic Republic"), + "LB" : QT_TRANSLATE_NOOP("ISO", "Lebanon"), + "LC" : QT_TRANSLATE_NOOP("ISO", "Saint Lucia"), + "LI" : QT_TRANSLATE_NOOP("ISO", "Liechtenstein"), + "LK" : QT_TRANSLATE_NOOP("ISO", "Sri Lanka"), + "LR" : QT_TRANSLATE_NOOP("ISO", "Liberia"), + "LS" : QT_TRANSLATE_NOOP("ISO", "Lesotho"), + "LT" : QT_TRANSLATE_NOOP("ISO", "Lithuania"), + "LU" : QT_TRANSLATE_NOOP("ISO", "Luxembourg"), + "LV" : QT_TRANSLATE_NOOP("ISO", "Latvia"), + "LY" : QT_TRANSLATE_NOOP("ISO", "Libya"), + "MA" : QT_TRANSLATE_NOOP("ISO", "Morocco"), + "MC" : QT_TRANSLATE_NOOP("ISO", "Monaco"), + "MD" : QT_TRANSLATE_NOOP("ISO", "Republic of Moldova"), + "ME" : QT_TRANSLATE_NOOP("ISO", "Montenegro"), + "MF" : QT_TRANSLATE_NOOP("ISO", "Saint Martin (French part)"), + "MG" : QT_TRANSLATE_NOOP("ISO", "Madagascar"), + "MH" : QT_TRANSLATE_NOOP("ISO", "Marshall Islands"), + "MK" : QT_TRANSLATE_NOOP("ISO", "The Former Yugoslav Republic of Macedonia"), + "ML" : QT_TRANSLATE_NOOP("ISO", "Mali"), + "MM" : QT_TRANSLATE_NOOP("ISO", "Myanmar"), + "MN" : QT_TRANSLATE_NOOP("ISO", "Mongolia"), + "MO" : QT_TRANSLATE_NOOP("ISO", "Macao"), + "MP" : QT_TRANSLATE_NOOP("ISO", "Northern Mariana Islands"), + "MQ" : QT_TRANSLATE_NOOP("ISO", "Martinique"), + "MR" : QT_TRANSLATE_NOOP("ISO", "Mauritania"), + "MS" : QT_TRANSLATE_NOOP("ISO", "Montserrat"), + "MT" : QT_TRANSLATE_NOOP("ISO", "Malta"), + "MU" : QT_TRANSLATE_NOOP("ISO", "Mauritius"), + "MV" : QT_TRANSLATE_NOOP("ISO", "Maldives"), + "MW" : QT_TRANSLATE_NOOP("ISO", "Malawi"), + "MX" : QT_TRANSLATE_NOOP("ISO", "Mexico"), + "MY" : QT_TRANSLATE_NOOP("ISO", "Malaysia"), + "MZ" : QT_TRANSLATE_NOOP("ISO", "Mozambique"), + "NA" : QT_TRANSLATE_NOOP("ISO", "Namibia"), + "NC" : QT_TRANSLATE_NOOP("ISO", "New Caledonia"), + "NE" : QT_TRANSLATE_NOOP("ISO", "Niger"), + "NF" : QT_TRANSLATE_NOOP("ISO", "Norfolk Island"), + "NG" : QT_TRANSLATE_NOOP("ISO", "Nigeria"), + "NI" : QT_TRANSLATE_NOOP("ISO", "Nicaragua"), + "NL" : QT_TRANSLATE_NOOP("ISO", "Netherlands"), + "NO" : QT_TRANSLATE_NOOP("ISO", "Norway"), + "NP" : QT_TRANSLATE_NOOP("ISO", "Nepal"), + "NR" : QT_TRANSLATE_NOOP("ISO", "Nauru"), + "NU" : QT_TRANSLATE_NOOP("ISO", "Niue"), + "NZ" : QT_TRANSLATE_NOOP("ISO", "New Zealand"), + "OM" : QT_TRANSLATE_NOOP("ISO", "Oman"), + "PA" : QT_TRANSLATE_NOOP("ISO", "Panama"), + "PE" : QT_TRANSLATE_NOOP("ISO", "Peru"), + "PF" : QT_TRANSLATE_NOOP("ISO", "French Polynesia"), + "PG" : QT_TRANSLATE_NOOP("ISO", "Papua New Guinea"), + "PH" : QT_TRANSLATE_NOOP("ISO", "Philippines"), + "PK" : QT_TRANSLATE_NOOP("ISO", "Pakistan"), + "PL" : QT_TRANSLATE_NOOP("ISO", "Poland"), + "PM" : QT_TRANSLATE_NOOP("ISO", "Saint Pierre and Miquelon"), + "PN" : QT_TRANSLATE_NOOP("ISO", "Pitcairn"), + "PR" : QT_TRANSLATE_NOOP("ISO", "Puerto Rico"), + "PS" : QT_TRANSLATE_NOOP("ISO", "State of Palestine"), + "PT" : QT_TRANSLATE_NOOP("ISO", "Portugal"), + "PW" : QT_TRANSLATE_NOOP("ISO", "Palau"), + "PY" : QT_TRANSLATE_NOOP("ISO", "Paraguay"), + "QA" : QT_TRANSLATE_NOOP("ISO", "Qatar"), + "RE" : QT_TRANSLATE_NOOP("ISO", "R\u00e9union"), + "RO" : QT_TRANSLATE_NOOP("ISO", "Romania"), + "RS" : QT_TRANSLATE_NOOP("ISO", "Serbia"), + "RU" : QT_TRANSLATE_NOOP("ISO", "Russian Federation"), + "RW" : QT_TRANSLATE_NOOP("ISO", "Rwanda"), + "SA" : QT_TRANSLATE_NOOP("ISO", "Saudi Arabia"), + "SB" : QT_TRANSLATE_NOOP("ISO", "Solomon Islands"), + "SC" : QT_TRANSLATE_NOOP("ISO", "Seychelles"), + "SD" : QT_TRANSLATE_NOOP("ISO", "Sudan"), + "SE" : QT_TRANSLATE_NOOP("ISO", "Sweden"), + "SG" : QT_TRANSLATE_NOOP("ISO", "Singapore"), + "SH" : QT_TRANSLATE_NOOP("ISO", "Saint Helena, Ascension and Tristan da Cunha"), + "SI" : QT_TRANSLATE_NOOP("ISO", "Slovenia"), + "SJ" : QT_TRANSLATE_NOOP("ISO", "Svalbard and Jan Mayen"), + "SK" : QT_TRANSLATE_NOOP("ISO", "Slovakia"), + "SL" : QT_TRANSLATE_NOOP("ISO", "Sierra Leone"), + "SM" : QT_TRANSLATE_NOOP("ISO", "San Marino"), + "SN" : QT_TRANSLATE_NOOP("ISO", "Senegal"), + "SO" : QT_TRANSLATE_NOOP("ISO", "Somalia"), + "SR" : QT_TRANSLATE_NOOP("ISO", "Suriname"), + "SS" : QT_TRANSLATE_NOOP("ISO", "South Sudan"), + "ST" : QT_TRANSLATE_NOOP("ISO", "Sao Tome and Principe"), + "SV" : QT_TRANSLATE_NOOP("ISO", "El Salvador"), + "SX" : QT_TRANSLATE_NOOP("ISO", "Sint Maarten"), + "SY" : QT_TRANSLATE_NOOP("ISO", "Syrian Arab Republic"), + "SZ" : QT_TRANSLATE_NOOP("ISO", "Swaziland"), + "TC" : QT_TRANSLATE_NOOP("ISO", "Turks and Caicos Islands"), + "TD" : QT_TRANSLATE_NOOP("ISO", "Chad"), + "TF" : QT_TRANSLATE_NOOP("ISO", "French Southern Territories"), + "TG" : QT_TRANSLATE_NOOP("ISO", "Togo"), + "TH" : QT_TRANSLATE_NOOP("ISO", "Thailand"), + "TJ" : QT_TRANSLATE_NOOP("ISO", "Tajikistan"), + "TK" : QT_TRANSLATE_NOOP("ISO", "Tokelau"), + "TL" : QT_TRANSLATE_NOOP("ISO", "Timor-Leste"), + "TM" : QT_TRANSLATE_NOOP("ISO", "Turkmenistan"), + "TN" : QT_TRANSLATE_NOOP("ISO", "Tunisia"), + "TO" : QT_TRANSLATE_NOOP("ISO", "Tonga"), + "TR" : QT_TRANSLATE_NOOP("ISO", "Turkey"), + "TT" : QT_TRANSLATE_NOOP("ISO", "Trinidad and Tobago"), + "TV" : QT_TRANSLATE_NOOP("ISO", "Tuvalu"), + "TW" : QT_TRANSLATE_NOOP("ISO", "Taiwan, Province of China"), + "TZ" : QT_TRANSLATE_NOOP("ISO", "United Republic of Tanzania"), + "UA" : QT_TRANSLATE_NOOP("ISO", "Ukraine"), + "UG" : QT_TRANSLATE_NOOP("ISO", "Uganda"), + "UM" : QT_TRANSLATE_NOOP("ISO", "United States Minor Outlying Islands"), + "US" : QT_TRANSLATE_NOOP("ISO", "United States"), + "UY" : QT_TRANSLATE_NOOP("ISO", "Uruguay"), + "UZ" : QT_TRANSLATE_NOOP("ISO", "Uzbekistan"), + "VA" : QT_TRANSLATE_NOOP("ISO", "Holy See (Vatican City State)"), + "VC" : QT_TRANSLATE_NOOP("ISO", "Saint Vincent and the Grenadines"), + "VE" : QT_TRANSLATE_NOOP("ISO", "Bolivarian Republic of Venezuela"), + "VG" : QT_TRANSLATE_NOOP("ISO", "British Virgin Islands"), + "VI" : QT_TRANSLATE_NOOP("ISO", "U.S. Virgin Islands"), + "VN" : QT_TRANSLATE_NOOP("ISO", "Viet Nam"), + "VU" : QT_TRANSLATE_NOOP("ISO", "Vanuatu"), + "WF" : QT_TRANSLATE_NOOP("ISO", "Wallis and Futuna"), + "WS" : QT_TRANSLATE_NOOP("ISO", "Samoa"), + "YE" : QT_TRANSLATE_NOOP("ISO", "Yemen"), + "YT" : QT_TRANSLATE_NOOP("ISO", "Mayotte"), + "ZA" : QT_TRANSLATE_NOOP("ISO", "South Africa"), + "ZM" : QT_TRANSLATE_NOOP("ISO", "Zambia"), + "ZW" : QT_TRANSLATE_NOOP("ISO", "Zimbabwe"), } # END Class isoCountry diff --git a/nw/core/document.py b/nw/core/document.py index 7e833854..15437ff6 100644 --- a/nw/core/document.py +++ b/nw/core/document.py @@ -24,9 +24,12 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ +from functools import partial import logging import os +from PyQt5.QtCore import QCoreApplication + from nw.constants import nwAlert from nw.common import isHandle from nw.constants import nwItemLayout, nwItemClass @@ -48,6 +51,7 @@ class NWDoc(): # Internal Mapping self.makeAlert = self.theParent.makeAlert + self.tr = partial(QCoreApplication.translate, self.__class__.__name__) return @@ -111,7 +115,7 @@ class NWDoc(): theText += inFile.read() except Exception as e: - self.makeAlert(["Failed to open document file.", str(e)], nwAlert.ERROR) + self.makeAlert([self.tr("Failed to open document file."), str(e)], nwAlert.ERROR) # Note: Document must be cleared in case of an io error, # or else the auto-save or save will try to overwrite it # with an empty file. Return None to alert the caller. @@ -124,7 +128,10 @@ class NWDoc(): return "" if showStatus and not isOrphan: - self.theParent.setStatus("Opened Document: %s" % self._theItem.itemName) + self.theParent.setStatus( + self.tr("{0}: {1}").format( + self.tr("Opened Document"), + self._theItem.itemName)) return theText @@ -158,7 +165,7 @@ class NWDoc(): outFile.write(docMeta) outFile.write(docText) except Exception as e: - self.makeAlert(["Could not save document.", str(e)], nwAlert.ERROR) + self.makeAlert([self.tr("Could not save document."), str(e)], nwAlert.ERROR) return False # If we're here, the file was successfully saved, so we can @@ -168,7 +175,10 @@ class NWDoc(): os.rename(docTemp, docPath) if self._theItem is not None: - self.theParent.setStatus("Saved Document: %s" % self._theItem.itemName) + self.theParent.setStatus( + self.tr("{0}: {1}").format( + self.tr("Saved Document"), + self._theItem.itemName)) return True @@ -191,7 +201,8 @@ class NWDoc(): os.unlink(chkFile) logger.debug("Deleted: %s" % chkFile) except Exception as e: - self.makeAlert(["Could not delete document file.", str(e)], nwAlert.ERROR) + self.makeAlert([self.tr("Could not delete document file."), str(e)], + nwAlert.ERROR) return False return True diff --git a/nw/core/index.py b/nw/core/index.py index ac1fba3f..9ac2ac45 100644 --- a/nw/core/index.py +++ b/nw/core/index.py @@ -24,6 +24,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ +from functools import partial import nw import logging import json @@ -31,6 +32,8 @@ import os from time import time +from PyQt5.QtCore import QCoreApplication + from nw.constants import ( nwFiles, nwKeyWords, nwItemType, nwItemClass, nwItemLayout, nwAlert ) @@ -62,9 +65,13 @@ class NWIndex(): # TimeStamps self._timeNovel = 0 - self._timeNotes = 0 + self._timeNotes = 0 self._timeIndex = 0 + self.tr = partial(QCoreApplication.translate, self.__class__.__name__) + + self.clearIndex() + return ## @@ -229,7 +236,7 @@ class NWIndex(): if self.indexBroken: self.clearIndex() self.theParent.makeAlert( - "The project index is outdated or broken. Rebuilding index.", + self.tr("The project index is outdated or broken. Rebuilding index."), nwAlert.WARN ) diff --git a/nw/core/project.py b/nw/core/project.py index fcb675ae..62cba7ca 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -24,6 +24,9 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ +from functools import partial + +from PyQt5.QtCore import QCoreApplication import nw import logging import os @@ -97,11 +100,12 @@ class NWProject(): self.notesWCount = 0 # Total number of words in note files self.doBackup = True # Run project backup on exit - # Set Defaults - self.clearProject() - # Internal Mapping self.makeAlert = self.theParent.makeAlert + self.tr = partial(QCoreApplication.translate, self.__class__.__name__) + + # Set Defaults + self.clearProject() return @@ -157,7 +161,7 @@ class NWProject(): trashHandle = self.projTree.trashRoot() if trashHandle is None: newItem = NWItem(self) - newItem.setName("Trash") + newItem.setName(self.tr("Trash")) newItem.setType(nwItemType.TRASH) newItem.setClass(nwItemClass.TRASH) self.projTree.append(None, None, newItem) @@ -197,7 +201,7 @@ class NWProject(): self.autoReplace = {} self.titleFormat = { "title" : r"%title%", - "chapter" : r"Chapter %ch%: %title%", + "chapter" : self.tr(r"Chapter %ch%: %title%"), "unnumbered" : r"%title%", "scene" : r"* * *", "section" : r"", @@ -205,15 +209,15 @@ class NWProject(): self.spellCheck = False self.autoOutline = True self.statusItems = NWStatus() - self.statusItems.addEntry("New", (100, 100, 100)) - self.statusItems.addEntry("Note", (200, 50, 0)) - self.statusItems.addEntry("Draft", (200, 150, 0)) - self.statusItems.addEntry("Finished", (50, 200, 0)) + self.statusItems.addEntry(self.tr("New"), (100, 100, 100)) + self.statusItems.addEntry(self.tr("Note"), (200, 50, 0)) + self.statusItems.addEntry(self.tr("Draft"), (200, 150, 0)) + self.statusItems.addEntry(self.tr("Finished"), (50, 200, 0)) self.importItems = NWStatus() - self.importItems.addEntry("New", (100, 100, 100)) - self.importItems.addEntry("Minor", (200, 50, 0)) - self.importItems.addEntry("Major", (200, 150, 0)) - self.importItems.addEntry("Main", (50, 200, 0)) + self.importItems.addEntry(self.tr("New"), (100, 100, 100)) + self.importItems.addEntry(self.tr("Minor"), (200, 50, 0)) + self.importItems.addEntry(self.tr("Major"), (200, 150, 0)) + self.importItems.addEntry(self.tr("Main"), (50, 200, 0)) self.lastEdited = None self.lastViewed = None self.lastWCount = 0 @@ -239,7 +243,7 @@ class NWProject(): # Project Settings projPath = projData.get("projPath", None) - projName = projData.get("projName", "New Project") + projName = projData.get("projName", self.tr("New Project")) projTitle = projData.get("projTitle", "") projAuthors = projData.get("projAuthors", "") @@ -256,7 +260,7 @@ class NWProject(): titlePage = "# %s\n\n" % (self.bookTitle if self.bookTitle else self.projName) if self.bookAuthors: - titlePage = "%sBy %s\n" % (titlePage, ", ".join(self.bookAuthors)) + titlePage = "%s%s %s\n" % (titlePage, self.tr("By"), ", ".join(self.bookAuthors)) # Document object for writing files aDoc = NWDoc(self, self.theParent) @@ -265,14 +269,14 @@ class NWProject(): # Creating a minimal project with a few root folders and a # single chapter folder with a single file. xHandle = {} - xHandle[1] = self.newRoot("Novel", nwItemClass.NOVEL) - xHandle[2] = self.newRoot("Plot", nwItemClass.PLOT) - xHandle[3] = self.newRoot("Characters", nwItemClass.CHARACTER) - xHandle[4] = self.newRoot("World", nwItemClass.WORLD) - xHandle[5] = self.newFile("Title Page", nwItemClass.NOVEL, xHandle[1]) - xHandle[6] = self.newFolder("New Chapter", nwItemClass.NOVEL, xHandle[1]) - xHandle[7] = self.newFile("New Chapter", nwItemClass.NOVEL, xHandle[6]) - xHandle[8] = self.newFile("New Scene", nwItemClass.NOVEL, xHandle[6]) + xHandle[1] = self.newRoot(self.tr("Novel"), nwItemClass.NOVEL) + xHandle[2] = self.newRoot(self.tr("Plot"), nwItemClass.PLOT) + xHandle[3] = self.newRoot(self.tr("Characters"), nwItemClass.CHARACTER) + xHandle[4] = self.newRoot(self.tr("World"), nwItemClass.WORLD) + xHandle[5] = self.newFile(self.tr("Title Page"), nwItemClass.NOVEL, xHandle[1]) + xHandle[6] = self.newFolder(self.tr("New Chapter"), nwItemClass.NOVEL, xHandle[1]) + xHandle[7] = self.newFile(self.tr("New Chapter"), nwItemClass.NOVEL, xHandle[6]) + xHandle[8] = self.newFile(self.tr("New Scene"), nwItemClass.NOVEL, xHandle[6]) self.projTree.setFileItemLayout(xHandle[5], nwItemLayout.TITLE) self.projTree.setFileItemLayout(xHandle[7], nwItemLayout.CHAPTER) @@ -282,11 +286,11 @@ class NWProject(): aDoc.clearDocument() aDoc.openDocument(xHandle[7], showStatus=False) - aDoc.saveDocument("## New Chapter\n\n") + aDoc.saveDocument("## %s\n\n" % self.tr("New Chapter")) aDoc.clearDocument() aDoc.openDocument(xHandle[8], showStatus=False) - aDoc.saveDocument("### New Scene\n\n") + aDoc.saveDocument("### %s\n\n" % self.tr("New Scene")) aDoc.clearDocument() elif popCustom: @@ -295,13 +299,14 @@ class NWProject(): # wizard's custom page. # Create root folders - nHandle = self.newRoot("Novel", nwItemClass.NOVEL) + nHandle = self.newRoot(self.tr("Novel"), nwItemClass.NOVEL) for newRoot in projData.get("addRoots", []): if newRoot in nwItemClass: - self.newRoot(nwLabels.CLASS_NAME[newRoot], newRoot) + self.newRoot(QCoreApplication.translate( + "Constant", nwLabels.CLASS_NAME[newRoot]), newRoot) # Create a title page - tHandle = self.newFile("Title Page", nwItemClass.NOVEL, nHandle) + tHandle = self.newFile(self.tr("Title Page"), nwItemClass.NOVEL, nHandle) self.projTree.setFileItemLayout(tHandle, nwItemLayout.TITLE) aDoc.openDocument(tHandle, showStatus=False) @@ -316,7 +321,7 @@ class NWProject(): # Create chapters if numChapters > 0: for ch in range(numChapters): - chTitle = "Chapter %d" % (ch+1) + chTitle = self.tr("Chapter {0}").format(f"{ch+1:d}") pHandle = nHandle if chFolders: pHandle = self.newFolder(chTitle, nwItemClass.NOVEL, nHandle) @@ -331,7 +336,7 @@ class NWProject(): # Create chapter scenes if numScenes > 0: for sc in range(numScenes): - scTitle = "Scene %d.%d" % (ch+1, sc+1) + scTitle = self.tr("Scene {0}").format(f"{ch+1:d}.{sc+1:d}") sHandle = self.newFile(scTitle, nwItemClass.NOVEL, pHandle) aDoc.openDocument(sHandle, showStatus=False) @@ -341,7 +346,7 @@ class NWProject(): # Create scenes (no chapters) elif numScenes > 0: for sc in range(numScenes): - scTitle = "Scene %d" % (sc+1) + scTitle = self.tr("Scene {0}").format(f"{sc+1:d}") sHandle = self.newFile(scTitle, nwItemClass.NOVEL, nHandle) aDoc.openDocument(sHandle, showStatus=False) @@ -365,7 +370,7 @@ class NWProject(): if not os.path.isfile(fileName): fileName = os.path.join(fileName, nwFiles.PROJ_FILE) if not os.path.isfile(fileName): - self.makeAlert("File not found: %s" % fileName, nwAlert.ERROR) + self.makeAlert(self.tr("File not found: {0}").format(fileName), nwAlert.ERROR) return False self.clearProject() @@ -414,16 +419,18 @@ class NWProject(): try: nwXML = etree.parse(fileName) except Exception as e: - self.makeAlert(["Failed to parse project xml.", str(e)], nwAlert.ERROR) + self.makeAlert([self.tr("Failed to parse project xml."), str(e)], nwAlert.ERROR) # Trying to open backup file instead backFile = fileName[:-3]+"bak" if os.path.isfile(backFile): - self.makeAlert("Attempting to open backup project file instead.", nwAlert.INFO) + self.makeAlert(self.tr("Attempting to open backup project file instead."), + nwAlert.INFO) try: nwXML = etree.parse(backFile) except Exception as e: - self.makeAlert(["Failed to parse project xml.", str(e)], nwAlert.ERROR) + self.makeAlert([self.tr("Failed to parse project xml."), str(e)], + nwAlert.ERROR) self.clearProject() return False else: @@ -433,9 +440,9 @@ class NWProject(): xRoot = nwXML.getroot() nwxRoot = xRoot.tag - appVersion = xRoot.attrib.get("appVersion", "Unknown") + appVersion = xRoot.attrib.get("appVersion", self.tr("Unknown")) hexVersion = xRoot.attrib.get("hexVersion", "0x0") - fileVersion = xRoot.attrib.get("fileVersion", "Unknown") + fileVersion = xRoot.attrib.get("fileVersion", self.tr("Unknown")) # The following are deprecated and will be removed # The settings have been moved to the tag @@ -451,7 +458,7 @@ class NWProject(): if not nwxRoot == "novelWriterXML": self.makeAlert( - "Project file does not appear to be a novelWriterXML file.", + self.tr("Project file does not appear to be a novelWriterXML file."), nwAlert.ERROR ) self.clearProject() @@ -470,24 +477,23 @@ class NWProject(): # read the file. Introduced in version 0.10. if fileVersion == "1.0": - msgYes = self.theParent.askQuestion("Old Project Version", ( - "The project file and data is created by a novelWriter version " - "lower than 0.7. Do you want to upgrade the project to the " - "most recent format?

Note that after the upgrade, you " - "cannot open the project with an older version of novelWriter " - "any more, so make sure you have a recent backup." - )) + msgYes = self.theParent.askQuestion(self.tr("Old Project Version"), ( + "%s

%s" % ( + self.tr("The project file and data is created by a novelWriter version " + "lower than 0.7. Do you want to upgrade the project to the " + "most recent format?"), + self.tr("Note that after the upgrade, you " + "cannot open the project with an older version of novelWriter " + "any more, so make sure you have a recent backup.")))) if not msgYes: self.clearProject() return False elif fileVersion != "1.1" and fileVersion != "1.2": self.makeAlert(( - "Unknown or unsupported novelWriter project file format. " - "The project cannot be opened by this version of novelWriter. " - "The file was saved with novelWriter version {vers:s}." - ).format( - vers = appVersion, + self.tr("Unknown or unsupported novelWriter project file format. " + "The project cannot be opened by this version of novelWriter. " + "The file was saved with novelWriter version {0}.").format(appVersion) ), nwAlert.ERROR) self.clearProject() return False @@ -496,13 +502,14 @@ class NWProject(): # ========================= if hexToInt(hexVersion) > hexToInt(nw.__hexversion__): - msgYes = self.theParent.askQuestion("Version Conflict", ( - "This project was saved by a newer version of novelWriter, version %s. " - "This is version %s. If you continue to open the project, some attributes " - "and settings may not be preserved, but the overall project should be fine. " - "Continue opening the project?" - ) % ( - appVersion, nw.__version__ + msgYes = self.theParent.askQuestion(self.tr("Version Conflict"), ( + self.tr("This project was saved by a newer version of novelWriter, version " + "{new_version}. This is version {version}. If you continue to open the " + "project, some attributes and settings may not be preserved, but the " + "overall project should be fine. Continue opening the project?") + ).format( + new_version = appVersion, + version = nw.__version__ )) if not msgYes: self.clearProject() @@ -602,7 +609,9 @@ class NWProject(): self.mainConf.updateRecentCache(self.projPath, self.projName, self.lastWCount, time()) self.mainConf.saveRecentCache() - self.theParent.setStatus("Opened Project: %s" % self.projName) + self.theParent.setStatus(self.tr("{0}: {1}").format( + self.tr("Opened Project"), + self.projName)) self._scanProjectFolder() @@ -623,7 +632,7 @@ class NWProject(): """ if self.projPath is None: self.makeAlert( - "Project path not set, cannot save project.", nwAlert.ERROR + self.tr("Project path not set, cannot save project."), nwAlert.ERROR ) return False @@ -702,7 +711,7 @@ class NWProject(): xml_declaration = True )) except Exception as e: - self.makeAlert(["Failed to save project.", str(e)], nwAlert.ERROR) + self.makeAlert([self.tr("Failed to save project."), str(e)], nwAlert.ERROR) return False # If we're here, the file was successfully saved, @@ -721,7 +730,9 @@ class NWProject(): self.mainConf.saveRecentCache() self._writeLockFile() - self.theParent.setStatus("Saved Project: %s" % self.projName) + self.theParent.setStatus(self.tr("{0}: {1}").format( + self.tr("Saved Project"), + self.projName)) self.setProjectChanged(False) return True @@ -773,26 +784,26 @@ class NWProject(): return False logger.info("Backing up project") - self.theParent.setStatus("Backing up project ...") + self.theParent.setStatus(self.tr("Backing up project ...")) if self.mainConf.backupPath is None or self.mainConf.backupPath == "": self.theParent.makeAlert(( - "Cannot backup project because no backup path is set. " - "Please set a valid backup location in Tools > Preferences." + self.tr("Cannot backup project because no backup path is set. " + "Please set a valid backup location in Tools > Preferences.") ), nwAlert.ERROR) return False if self.projName is None or self.projName == "": self.theParent.makeAlert(( - "Cannot backup project because no project name is set. " - "Please set a Working Title in Project > Project Settings." + self.tr("Cannot backup project because no project name is set. " + "Please set a Working Title in Project > Project Settings.") ), nwAlert.ERROR) return False if not os.path.isdir(self.mainConf.backupPath): self.theParent.makeAlert(( - "Cannot backup project because the backup path does not exist. " - "Please set a valid backup location in Tools > Preferences." + self.tr("Cannot backup project because the backup path does not exist. " + "Please set a valid backup location in Tools > Preferences.") ), nwAlert.ERROR) return False @@ -804,20 +815,20 @@ class NWProject(): logger.debug("Created folder %s" % baseDir) except Exception as e: self.theParent.makeAlert( - ["Could not create backup folder.", str(e)], + [self.tr("Could not create backup folder."), str(e)], nwAlert.ERROR ) return False if os.path.commonpath([self.projPath, baseDir]) == self.projPath: self.theParent.makeAlert(( - "Cannot backup project because the backup path is within the " - "project folder to be backed up. Please choose a different " - "backup path in Tools > Preferences." + self.tr("Cannot backup project because the backup path is within the " + "project folder to be backed up. Please choose a different " + "backup path in Tools > Preferences.") ), nwAlert.ERROR) return False - archName = "Backup from %s" % formatTimeStamp(time(), fileSafe=True) + archName = self.tr("Backup from {0}").format(formatTimeStamp(time(), fileSafe=True)) baseName = os.path.join(baseDir, archName) try: @@ -827,18 +838,19 @@ class NWProject(): logger.info("Backup written to: %s" % archName) if doNotify: self.theParent.makeAlert( - "Backup archive file written to: %s.zip" % os.path.join(cleanName, archName), + self.tr("Backup archive file written to: {0}").format( + f"{os.path.join(cleanName, archName)}.zip"), nwAlert.INFO ) except Exception as e: self.theParent.makeAlert( - ["Could not write backup archive.", str(e)], + [self.tr("Could not write backup archive."), str(e)], nwAlert.ERROR ) return False - self.theParent.setStatus("Project backed up to '%s.zip'" % baseName) + self.theParent.setStatus(self.tr("Project backed up to '{0}'").format(f"{baseName}.zip")) return True @@ -853,8 +865,9 @@ class NWProject(): logger.error("No project path set for the example project") return False - srcSample = os.path.abspath(os.path.join(self.mainConf.appRoot, "sample")) - pkgSample = os.path.join(self.mainConf.assetPath, "sample.zip") + srcSample = os.path.abspath(os.path.join(self.mainConf.appRoot, + self.tr("sample"))) + pkgSample = os.path.join(self.mainConf.assetPath, "%s.zip" % self.tr("sample")) isSuccess = False if os.path.isfile(pkgSample): @@ -865,7 +878,7 @@ class NWProject(): isSuccess = True except Exception as e: self.makeAlert( - ["Failed to create a new example project.", str(e)], nwAlert.ERROR + [self.tr("Failed to create a new example project."), str(e)], nwAlert.ERROR ) elif os.path.isdir(srcSample): @@ -876,8 +889,8 @@ class NWProject(): dstProj = os.path.join(projPath, nwFiles.PROJ_FILE) shutil.copyfile(srcProj, dstProj) - srcContent = os.path.join(srcSample, "content") - dstContent = os.path.join(projPath, "content") + srcContent = os.path.join(srcSample, self.tr("content")) + dstContent = os.path.join(projPath, self.tr("content")) for srcFile in os.listdir(srcContent): srcDoc = os.path.join(srcContent, srcFile) dstDoc = os.path.join(dstContent, srcFile) @@ -887,13 +900,13 @@ class NWProject(): except Exception as e: self.makeAlert( - ["Failed to create a new example project.", str(e)], nwAlert.ERROR + [self.tr("Failed to create a new example project."), str(e)], nwAlert.ERROR ) else: self.makeAlert(( - "Failed to create a new example project. Could not find the " - "necessary files. They seem to be missing from this installation." + self.tr("Failed to create a new example project. Could not find the " + "necessary files. They seem to be missing from this installation.") ), nwAlert.ERROR) if isSuccess: @@ -925,15 +938,15 @@ class NWProject(): logger.debug("Created folder %s" % projPath) except Exception as e: self.theParent.makeAlert(( - ["Could not create new project folder.", str(e)] + [self.tr("Could not create new project folder."), str(e)] ), nwAlert.ERROR) return False if os.path.isdir(projPath): if os.listdir(self.projPath): self.theParent.makeAlert(( - "New project folder is not empty. " - "Each project requires a dedicated project folder." + self.tr("New project folder is not empty. " + "Each project requires a dedicated project folder.") ), nwAlert.ERROR) return False @@ -982,15 +995,15 @@ class NWProject(): if doBackup: if not os.path.isdir(self.mainConf.backupPath): self.theParent.makeAlert(( - "You must set a valid backup path in preferences to use " - "the automatic project backup feature." + self.tr("You must set a valid backup path in preferences to use " + "the automatic project backup feature.") ), nwAlert.WARN) return False if self.projName == "": self.theParent.makeAlert(( - "You must set a valid project name in project settings to " - "use the automatic project backup feature." + self.tr("You must set a valid project name in project settings to " + "use the automatic project backup feature.") ), nwAlert.WARN) return False @@ -1330,7 +1343,7 @@ class NWProject(): # Report status if len(orphanFiles) > 0: self.makeAlert( - "Found %d orphaned file(s) in project folder." % len(orphanFiles), + self.tr("Found {0} orphaned file(s) in project folder.").format(len(orphanFiles)), nwAlert.WARN ) else: @@ -1352,10 +1365,12 @@ class NWProject(): oName, oParent, oClass, oLayout = aDoc.getMeta() if oName: - oName = "Recovered: %s" % oName.lstrip("Recovered: ") + oName = self.tr("{0}: {1}").format( + self.tr("Recovered"), + oName.lstrip(self.tr("{0}: ").format(self.tr("Recovered")))) else: nOrph += 1 - oName = "Recovered File %d" % nOrph + oName = self.tr("Recovered File {0}").format(nOrph) # Recover file meta data if oClass is None: @@ -1383,8 +1398,8 @@ class NWProject(): if noWhere: self.makeAlert(( - "One or more orphaned files could not be added back into the " - "project. Make sure at least a Novel root folder exists." + self.tr("One or more orphaned files could not be added back into the " + "project. Make sure at least a Novel root folder exists.") ), nwAlert.WARN) return True @@ -1403,9 +1418,13 @@ class NWProject(): if not isFile: # It's a new file, so add a header if self.lastWCount > 0: - outFile.write("# Offset %d\n" % self.lastWCount) + outFile.write("# %s\n" % self.tr("Offset {0}").format(self.lastWCount)) outFile.write("# %-17s %-19s %8s %8s %8s\n" % ( - "Start Time", "End Time", "Novel", "Notes", "Idle" + self.tr("Start Time"), + self.tr("End Time"), + self.tr("Novel"), + self.tr("Notes"), + self.tr("Idle"), )) outFile.write("%-19s %-19s %8d %8d %8d\n" % ( @@ -1432,7 +1451,7 @@ class NWProject(): """ theData = os.path.join(self.projPath, theFolder) if not os.path.isdir(theData): - errList.append("Not a folder: %s" % theData) + errList.append(self.tr("Not a folder: {0}").format(theData)) return errList logger.info("Old data folder %s found" % theFolder) @@ -1452,10 +1471,10 @@ class NWProject(): newPath = os.path.join(self.projContent, tHandle+".nwd") try: os.rename(theFile, newPath) - logger.info("Moved file: %s" % theFile) - logger.info("New location: %s" % newPath) + logger.info(self.tr("{0}: {1}").format(self.tr("Moved file"), theFile)) + logger.info(self.tr("{0}: {1}").format(self.tr("New location"), newPath)) except Exception: - errList.append("Could not move: %s" % theFile) + errList.append(self.tr("{0}: {1}").format(self.tr("Could not move"), theFile)) logger.error("Could not move: %s" % theFile) nw.logException() @@ -1464,7 +1483,8 @@ class NWProject(): os.unlink(theFile) logger.info("Deleted file: %s" % theFile) except Exception: - errList.append("Could not delete: %s" % theFile) + errList.append(self.tr("{0}: {1}").format( + self.tr("Could not delete"), theFile)) logger.error("Could not delete: %s" % theFile) nw.logException() @@ -1479,7 +1499,7 @@ class NWProject(): os.rmdir(theData) logger.info("Removed folder: %s" % theFolder) except Exception: - errList.append("Failed to remove: %s" % theFolder) + errList.append(self.tr("{0}: {1}").format(self.tr("Failed to remove"), theFolder)) logger.error("Failed to remove: %s" % theFolder) nw.logException() @@ -1489,9 +1509,9 @@ class NWProject(): """Move an item that doesn't belong in the project folder to a junk folder. """ - theJunk = os.path.join(self.projPath, "junk") + theJunk = os.path.join(self.projPath, self.tr("junk")) if not self._checkFolder(theJunk): - return "Could not make folder: %s" % theJunk + return self.tr("{0}: {1}").format(self.tr("Could not make folder"), theJunk) theSrc = os.path.join(theDir, theItem) theDst = os.path.join(theJunk, theItem) @@ -1502,7 +1522,7 @@ class NWProject(): except Exception: logger.error("Could not move item %s to junk." % theSrc) nw.logException() - return "Could not move item %s to junk." % theSrc + return self.tr("Could not move item {0} to junk.").format(theSrc) return "" diff --git a/nw/core/spellcheck.py b/nw/core/spellcheck.py index 9d22afcd..4aeb9841 100644 --- a/nw/core/spellcheck.py +++ b/nw/core/spellcheck.py @@ -24,6 +24,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ +from PyQt5.QtCore import QCoreApplication import nw import logging import os @@ -94,7 +95,7 @@ class NWSpellCheck(): """Translate a language tag to something more user friendly. """ spBits = spTag.split("_") - spLang = isoLanguage.ISO_639_1.get(spBits[0], spBits[0]) + spLang = QCoreApplication.translate("ISO", isoLanguage.ISO_639_1.get(spBits[0], spBits[0])) if len(spBits) > 1: spLang += " (%s)" % spBits[1] return spLang @@ -332,7 +333,9 @@ class NWSpellSimple(NWSpellCheck): if fExt != ".dict": continue - spName = "%s [%s]" % (self.expandLanguage(fRoot), nwConst.SP_INTERNAL) + spName = "%s [%s]" % ( + self.expandLanguage(fRoot), + QCoreApplication.translate("Constant", nwConst.SP_INTERNAL)) retList.append((fRoot, spName)) return retList @@ -341,6 +344,6 @@ class NWSpellSimple(NWSpellCheck): """Return the tag and provider of the currently loaded dictionary. """ - return self.theLang, nwConst.SP_INTERNAL + return self.theLang, QCoreApplication.translate("Constant", nwConst.SP_INTERNAL) # END Class NWSpellSimple diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py index 67ebcc0f..e8500168 100644 --- a/nw/core/tohtml.py +++ b/nw/core/tohtml.py @@ -406,9 +406,15 @@ class ToHtml(Tokenizer): """Apply HTML formatting to synopsis. """ if self.genMode == self.M_PREVIEW: - return "

Synopsis: %s

\n" % tText + return "

%s: %s

\n" % ( + self.tr("Synopsis"), + tText + ) else: - return "

Synopsis: %s

\n" % tText + return "

%s: %s

\n" % ( + self.tr("Synopsis"), + tText + ) def _formatComments(self, tText): """Apply HTML formatting to comments. @@ -416,7 +422,7 @@ class ToHtml(Tokenizer): if self.genMode == self.M_PREVIEW: return "

%s

\n" % tText else: - return "

Comment: %s

\n" % tText + return "

%s: %s

\n" % (self.tr("Comment"), tText) def _formatKeywords(self, tText): """Apply HTML formatting to keywords. diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py index 3e0474ae..27136879 100644 --- a/nw/core/tokenizer.py +++ b/nw/core/tokenizer.py @@ -24,11 +24,12 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ +from functools import partial import logging import re from operator import itemgetter -from PyQt5.QtCore import QRegularExpression +from PyQt5.QtCore import QCoreApplication, QRegularExpression from nw.core.document import NWDoc from nw.core.tools import numberToWord, numberToRoman @@ -141,6 +142,8 @@ class Tokenizer(): # Error Handling self.errData = [] + self.tr = partial(QCoreApplication.translate, self.__class__.__name__) + return ## @@ -249,7 +252,7 @@ class Tokenizer(): if theItem.itemType != nwItemType.ROOT: return False - theTitle = "Notes: %s" % theItem.itemName + theTitle = self.tr("{0}: {1}").format(self.tr("Notes"), theItem.itemName) self.theTokens = [] self.theTokens.append(( self.T_TITLE, 0, theTitle, None, self.A_PBB | self.A_CENTRE @@ -278,10 +281,11 @@ class Tokenizer(): docSize = len(self.theText) if docSize > nwConst.MAX_DOCSIZE: - errVal = "Document '%s' is too big (%.2f MB). Skipping." % ( - self.theItem.itemName, docSize/1.0e6 + errVal = self.tr("Document '{doc_name}' is too big ({doc_size}). Skipping.").format( + doc_name = self.theItem.itemName, + doc_size = f"{docSize/1.0e6:.2f} MB" ) - self.theText = "# ERROR\n\n%s\n\n" % errVal + self.theText = "# %s\n\n%s\n\n" % (self.tr("ERROR"), errVal) self.errData.append(errVal) self.isNone = self.theItem.itemLayout == nwItemLayout.NO_LAYOUT diff --git a/nw/core/tools.py b/nw/core/tools.py index 44b4c865..91b59c93 100644 --- a/nw/core/tools.py +++ b/nw/core/tools.py @@ -127,7 +127,7 @@ def numberToWord(numVal, theLanguage): """Wrapper for converting numbers to words for chapter headings. """ numWord = "" - if theLanguage == "en": + if theLanguage == "en": # TODO: I18N numWord = _numberToWordEN(numVal) else: numWord = _numberToWordEN(numVal) diff --git a/nw/core/tree.py b/nw/core/tree.py index 5fdc55ba..d614b4b9 100644 --- a/nw/core/tree.py +++ b/nw/core/tree.py @@ -24,6 +24,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ +from functools import partial import nw import logging import os @@ -32,6 +33,8 @@ from lxml import etree from hashlib import sha256 from time import time +from PyQt5.QtCore import QCoreApplication + from nw.core.item import NWItem from nw.common import checkHandle from nw.constants import ( @@ -79,6 +82,8 @@ class NWTree(): self._handleSeed = None # Used for generating handles for testing + self.tr = partial(QCoreApplication.translate, self.__class__.__name__) + return ## @@ -195,11 +200,14 @@ class NWTree(): tocText = os.path.join(self.theProject.projPath, nwFiles.TOC_TXT) with open(tocText, mode="w", encoding="utf8") as outFile: outFile.write("\n") - outFile.write("Table of Contents\n") + outFile.write("%s\n" % self.tr("Table of Contents")) outFile.write("=================\n") outFile.write("\n") outFile.write("%-25s %-9s %-10s %s\n" % ( - "File Name", "Class", "Layout", "Document Label" + self.tr("File Name"), + self.tr("Class"), + self.tr("Layout"), + self.tr("Document Label"), )) outFile.write("-"*tocLen + "\n") outFile.write("\n".join(tocList)) diff --git a/nw/error.py b/nw/error.py index 998b7347..c29e3eb6 100644 --- a/nw/error.py +++ b/nw/error.py @@ -68,6 +68,7 @@ class NWErrorMessage(QDialog): self.msgBody.setReadOnly(True) self.btnBox = QDialogButtonBox(QDialogButtonBox.Close) + self.btnBox.button(QDialogButtonBox.Close).setText(self.tr("Close")) self.btnBox.rejected.connect(self._doClose) # Assemble diff --git a/nw/gui/about.py b/nw/gui/about.py index d584cb25..9e90c5f8 100644 --- a/nw/gui/about.py +++ b/nw/gui/about.py @@ -55,14 +55,14 @@ class GuiAbout(QDialog): self.innerBox = QHBoxLayout() self.innerBox.setSpacing(self.mainConf.pxInt(16)) - self.setWindowTitle("About novelWriter") + self.setWindowTitle(self.tr("About novelWriter")) self.setMinimumWidth(self.mainConf.pxInt(650)) self.setMinimumHeight(self.mainConf.pxInt(600)) nPx = self.mainConf.pxInt(96) self.nwIcon = QLabel() self.nwIcon.setPixmap(self.theParent.theTheme.getPixmap("novelwriter", (nPx, nPx))) - self.lblName = QLabel("novelWriter") + self.lblName = QLabel("%s" % self.tr("novelWriter")) self.lblVers = QLabel("v%s" % nw.__version__) self.lblDate = QLabel(datetime.strptime(nw.__date__, "%Y-%m-%d").strftime("%x")) @@ -90,13 +90,14 @@ class GuiAbout(QDialog): # Main Tab Area self.tabBox = QTabWidget() - self.tabBox.addTab(self.pageAbout, "About") - self.tabBox.addTab(self.pageNotes, "Release") - self.tabBox.addTab(self.pageLicense, "License") + self.tabBox.addTab(self.pageAbout, self.tr("About")) + self.tabBox.addTab(self.pageNotes, self.tr("Release")) + self.tabBox.addTab(self.pageLicense, self.tr("License")) self.innerBox.addWidget(self.tabBox) # OK Button self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok) + self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("OK")) self.buttonBox.accepted.connect(self._doClose) self.outerBox.addLayout(self.innerBox) @@ -132,28 +133,30 @@ class GuiAbout(QDialog): """Generate the content for the About page. """ listPrefix = "  •  " - aboutMsg = ( - "

About novelWriter

" - "

{copyright:s}.

" - "

Website: {domain:s}

" - "

novelWriter is a markdown-like text editor designed for " - "organising and writing novels. It is written in Python 3 with a " - "Qt5 GUI, using PyQt5.

" - "

novelWriter 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 3 of " - "the License, or (at your option) any later version.

" - "

novelWriter 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 License tab for the full license text, or visit the " - "GNU website at " - "GPL v3.0 " - "for more details.

" - "

Credits

" - "

{credits:s}

" - ).format( + aboutMsg = "".join([ + "

%s

" % self.tr("About novelWriter"), + "

{copyright:s}.

", + "

%s

" % (self.tr("{0}: {1}").format( + self.tr("Website"), + "{domain:s}" + )), + "

%s

" % self.tr("novelWriter is a markdown-like text editor designed for " + "organising and writing novels. It is written in Python 3 with " + "a Qt5 GUI, using PyQt5."), + "

%s

" % self.tr("novelWriter 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 3 of " + "the License, or (at your option) any later version."), + "

%s

" % self.tr("novelWriter 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."), + "

%s

" % (self.tr("See the License tab for the full license text, or visit the " + "GNU website at {0} for more details.").format( + "GPL v3.0")), + "

%s

" % self.tr("Credits"), + "

{credits:s}

", + ]).format( copyright = nw.__copyright__, website = nw.__url__, domain = nw.__domain__, @@ -163,50 +166,52 @@ class GuiAbout(QDialog): theTheme = self.theParent.theTheme theIcons = self.theParent.theTheme.theIcons if theTheme.themeName: - aboutMsg += ( - "

Theme: {name:s}

" - "

" - "Author: {author:s}
" - "Credit: {credit:s}
" - "License: {license:s}" + aboutMsg += "".join([ + ("

%s

" % self.tr("{0}: {1}").format(self.tr("Theme"), theTheme.themeName)), + "

", + ("%s
" % self.tr("{0}: {1}").format( + self.tr("Author"), theTheme.themeAuthor)), + ("%s
" % self.tr("{0}: {1}").format( + self.tr("Credit"), theTheme.themeCredit)), + (self.tr("{0}: {1}").format( + self.tr("License"), + "{1}".format( + theTheme.themeLicenseUrl, theTheme.themeLicense) + )), "

" - ).format( - name = theTheme.themeName, - author = theTheme.themeAuthor, - credit = theTheme.themeCredit, - license = theTheme.themeLicense, - lic_url = theTheme.themeLicenseUrl, - ) + ]) if theIcons.themeName: - aboutMsg += ( - "

Icons: {name:s}

" - "

" - "Author: {author:s}
" - "Credit: {credit:s}
" - "License: {license:s}" + aboutMsg += "".join([ + ("

%s

" % self.tr("{0}: {1}").format(self.tr("Icons"), theIcons.themeName)), + "

", + ("%s
" % self.tr("{0}: {1}").format( + self.tr("Author"), theIcons.themeAuthor)), + ("%s
" % self.tr("{0}: {1}").format( + self.tr("Credit"), theIcons.themeCredit)), + (self.tr("{0}: {1}").format( + self.tr("License"), + "{1}".format( + theIcons.themeLicenseUrl, theIcons.themeLicense) + )), "

" - ).format( - name = theIcons.themeName, - author = theIcons.themeAuthor, - credit = theIcons.themeCredit, - license = theIcons.themeLicense, - lic_url = theIcons.themeLicenseUrl, - ) + ]) if theTheme.syntaxName: - aboutMsg += ( - "

Syntax: {name:s}

" - "

" - "Author: {author:s}
" - "Credit: {credit:s}
" - "License: {license:s}" + aboutMsg += "".join([ + ("

%s

" % self.tr("{0}: {1}").format( + self.tr("Syntax"), + theTheme.syntaxName)), + "

", + ("%s
" % self.tr("{0}: {1}").format( + self.tr("Author"), theTheme.syntaxAuthor)), + ("%s
" % self.tr("{0{0} {{1}").format( + self.tr("Credit"), theTheme.syntaxCredit)), + (self.tr("{0}: {1}").format( + self.tr("License"), + "{1}".format( + theTheme.syntaxLicenseUrl, theTheme.syntaxLicense) + )), "

" - ).format( - name = theTheme.syntaxName, - author = theTheme.syntaxAuthor, - credit = theTheme.syntaxCredit, - license = theTheme.syntaxLicense, - lic_url = theTheme.syntaxLicenseUrl, - ) + ]) self.pageAbout.setHtml(aboutMsg) diff --git a/nw/gui/build.py b/nw/gui/build.py index 3b76248f..33ba23d6 100644 --- a/nw/gui/build.py +++ b/nw/gui/build.py @@ -84,7 +84,7 @@ class GuiBuildNovel(QDialog): self.htmlSize = 0 # Size of the html document self.buildTime = 0 # The timestamp of the last build - self.setWindowTitle("Build Novel Project") + self.setWindowTitle(self.tr("Build Novel Project")) self.setMinimumWidth(self.mainConf.pxInt(700)) self.setMinimumHeight(self.mainConf.pxInt(600)) @@ -101,26 +101,26 @@ class GuiBuildNovel(QDialog): # Title Formats # ============= - self.titleGroup = QGroupBox("Title Formats for Novel Files", self) + self.titleGroup = QGroupBox(self.tr("Title Formats for Novel Files"), self) self.titleForm = QGridLayout(self) self.titleGroup.setLayout(self.titleForm) - fmtHelp = ( - r"Formatting Codes:
" - r"%title% for the title as set in the document
" - r"%ch% for chapter number (1, 2, 3)
" - r"%chw% for chapter number as a word (one, two)
" - r"%chI% for chapter number in upper case Roman
" - r"%chi% for chapter number in lower case Roman
" - r"%sc% for scene number within chapter
" - r"%sca% for scene number within novel" + fmtHelp = "
".join( + "%s" % self.tr("{0}:").format("Formatting Codes"), + self.tr("{0} for the title as set in the document").format(r"%title%"), + self.tr("{0} for chapter number (1, 2, 3)").format(r"%ch%"), + self.tr("{0} for chapter number as a word (one, two)").format(r"%chw%"), + self.tr("{0} for chapter number in upper case Roman").format(r"%chI%"), + self.tr("{0} for chapter number in lower case Roman").format(r"%chi%"), + self.tr("{0} for scene number within chapter").format(r"%sc%"), + self.tr("{0} for scene number within novel").format(r"%sca%"), ) fmtScHelp = ( - r"

" - r"Leave blank to skip this heading, or set to a static text, like " - r"for instance '* * *', to make a separator. The separator will " - r"be centred automatically and only appear between sections of " - r"the same type." + "

%s" % + self.tr("Leave blank to skip this heading, or set to a static text, like " + "for instance '{0}', to make a separator. The separator will " + "be centred automatically and only appear between sections of " + "the same type.").format("* * *") ) xFmt = self.mainConf.pxInt(100) @@ -176,11 +176,11 @@ class GuiBuildNovel(QDialog): self.boxSection = QHBoxLayout() self.boxSection.addWidget(self.fmtSection) - titleLabel = QLabel("Title") - chapterLabel = QLabel("Chapter") - unnumbLabel = QLabel("Unnumbered") - sceneLabel = QLabel("Scene") - sectionLabel = QLabel("Section") + titleLabel = QLabel(self.tr("Title")) + chapterLabel = QLabel(self.tr("Chapter")) + unnumbLabel = QLabel(self.tr("Unnumbered")) + sceneLabel = QLabel(self.tr("Scene")) + sectionLabel = QLabel(self.tr("Section")) self.titleForm.addWidget(titleLabel, 0, 0, 1, 1, Qt.AlignLeft) self.titleForm.addLayout(self.boxTitle, 0, 1, 1, 1, Qt.AlignRight) @@ -199,7 +199,7 @@ class GuiBuildNovel(QDialog): # Font Options # ============ - self.fontGroup = QGroupBox("Font Options", self) + self.fontGroup = QGroupBox(self.tr("Font Options"), self) self.fontForm = QGridLayout(self) self.fontGroup.setLayout(self.fontForm) @@ -237,11 +237,11 @@ class GuiBuildNovel(QDialog): self.boxFont = QHBoxLayout() self.boxFont.addWidget(self.textFont) - fontFamilyLabel = QLabel("Font family") - fontSizeLabel = QLabel("Font size") - lineHeightLabel = QLabel("Line height") - justifyLabel = QLabel("Justify text") - stylingLabel = QLabel("Disable styling") + fontFamilyLabel = QLabel(self.tr("Font family")) + fontSizeLabel = QLabel(self.tr("Font size")) + lineHeightLabel = QLabel(self.tr("Line height")) + justifyLabel = QLabel(self.tr("Justify text")) + stylingLabel = QLabel(self.tr("Disable styling")) self.fontForm.addWidget(fontFamilyLabel, 0, 0, 1, 1, Qt.AlignLeft) self.fontForm.addLayout(self.boxFont, 0, 1, 1, 1, Qt.AlignRight) @@ -283,7 +283,7 @@ class GuiBuildNovel(QDialog): # Include Options # =============== - self.textGroup = QGroupBox("Include Options", self) + self.textGroup = QGroupBox(self.tr("Include Options"), self) self.textForm = QGridLayout(self) self.textGroup.setLayout(self.textForm) @@ -307,10 +307,10 @@ class GuiBuildNovel(QDialog): self.optState.getBool("GuiBuildNovel", "incBodyText", True) ) - synopsisLabel = QLabel("Include synopsis") - commentsLabel = QLabel("Include comments") - keywordsLabel = QLabel("Include keywords") - bodyLabel = QLabel("Include body text") + synopsisLabel = QLabel(self.tr("Include synopsis")) + commentsLabel = QLabel(self.tr("Include comments")) + keywordsLabel = QLabel(self.tr("Include keywords")) + bodyLabel = QLabel(self.tr("Include body text")) self.textForm.addWidget(synopsisLabel, 0, 0, 1, 1, Qt.AlignLeft) self.textForm.addWidget(self.includeSynopsis, 0, 1, 1, 1, Qt.AlignRight) @@ -327,37 +327,37 @@ class GuiBuildNovel(QDialog): # File Filter Options # =================== - self.fileGroup = QGroupBox("File Filter Options", self) + self.fileGroup = QGroupBox(self.tr("File Filter Options"), self) self.fileForm = QGridLayout(self) self.fileGroup.setLayout(self.fileForm) self.novelFiles = QSwitch(width=wS, height=hS) self.novelFiles.setToolTip( - "Include files with layouts 'Book', 'Page', 'Partition', " - "'Chapter', 'Unnumbered', and 'Scene'." + self.tr("Include files with layouts 'Book', 'Page', 'Partition', " + "'Chapter', 'Unnumbered', and 'Scene'.") ) self.novelFiles.setChecked( self.optState.getBool("GuiBuildNovel", "addNovel", True) ) self.noteFiles = QSwitch(width=wS, height=hS) - self.noteFiles.setToolTip("Include files with layout 'Note'.") + self.noteFiles.setToolTip(self.tr("Include files with layout 'Note'.")) self.noteFiles.setChecked( self.optState.getBool("GuiBuildNovel", "addNotes", False) ) self.ignoreFlag = QSwitch(width=wS, height=hS) self.ignoreFlag.setToolTip( - "Ignore the 'Include when building project' setting and include " - "all files in the output." + self.tr("Ignore the 'Include when building project' setting and include " + "all files in the output.") ) self.ignoreFlag.setChecked( self.optState.getBool("GuiBuildNovel", "ignoreFlag", False) ) - novelLabel = QLabel("Include novel files") - notesLabel = QLabel("Include note files") - exportLabel = QLabel("Ignore export flag") + novelLabel = QLabel(self.tr("Include novel files")) + notesLabel = QLabel(self.tr("Include note files")) + exportLabel = QLabel(self.tr("Ignore export flag")) self.fileForm.addWidget(novelLabel, 0, 0, 1, 1, Qt.AlignLeft) self.fileForm.addWidget(self.novelFiles, 0, 1, 1, 1, Qt.AlignRight) @@ -372,7 +372,7 @@ class GuiBuildNovel(QDialog): # Export Options # ============== - self.exportGroup = QGroupBox("Export Options", self) + self.exportGroup = QGroupBox(self.tr("Export Options"), self) self.exportForm = QGridLayout(self) self.exportGroup.setLayout(self.exportForm) @@ -386,8 +386,8 @@ class GuiBuildNovel(QDialog): self.optState.getBool("GuiBuildNovel", "replaceUCode", False) ) - tabsLabel = QLabel("Replace tabs with spaces") - uCodeLabel = QLabel("Replace Unicode in HTML") + tabsLabel = QLabel(self.tr("Replace tabs with spaces")) + uCodeLabel = QLabel(self.tr("Replace Unicode in HTML")) self.exportForm.addWidget(tabsLabel, 0, 0, 1, 1, Qt.AlignLeft) self.exportForm.addWidget(self.replaceTabs, 0, 1, 1, 1, Qt.AlignRight) @@ -402,7 +402,7 @@ class GuiBuildNovel(QDialog): self.buildProgress = QProgressBar() - self.buildNovel = QPushButton("Build Preview") + self.buildNovel = QPushButton(self.tr("Build Preview")) self.buildNovel.clicked.connect(self._buildPreview) # Action Buttons @@ -413,14 +413,14 @@ class GuiBuildNovel(QDialog): # Printing self.printMenu = QMenu(self) - self.btnPrint = QPushButton("Print") + self.btnPrint = QPushButton(self.tr("Print")) self.btnPrint.setMenu(self.printMenu) - self.printSend = QAction("Print Preview", self) + self.printSend = QAction(self.tr("Print Preview"), self) self.printSend.triggered.connect(self._printDocument) self.printMenu.addAction(self.printSend) - self.printFile = QAction("Print to PDF", self) + self.printFile = QAction(self.tr("Print to PDF"), self) self.printFile.triggered.connect(lambda: self._saveDocument(self.FMT_PDF)) self.printMenu.addAction(self.printFile) @@ -430,39 +430,46 @@ class GuiBuildNovel(QDialog): self.btnSave = QPushButton("Save As") self.btnSave.setMenu(self.saveMenu) - self.saveODT = QAction("Open Document (.odt)", self) + self.saveODT = QAction(self.tr("{0} ({1})").format(self.tr("Open Document"), ".odt"), self) self.saveODT.triggered.connect(lambda: self._saveDocument(self.FMT_ODT)) self.saveMenu.addAction(self.saveODT) - self.saveFODT = QAction("Flat Open Document (.fodt)", self) + self.saveFODT = QAction( + self.tr("{0} ({1})").format(self.tr("Flat Open Document"), ".fodt"), self) self.saveFODT.triggered.connect(lambda: self._saveDocument(self.FMT_FODT)) self.saveMenu.addAction(self.saveFODT) - self.saveHTM = QAction("novelWriter HTML (.htm)", self) + self.saveHTM = QAction(self.tr("{0} ({1})").format( + self.tr("novelWriter HTML"), ".htm"), self) self.saveHTM.triggered.connect(lambda: self._saveDocument(self.FMT_HTM)) self.saveMenu.addAction(self.saveHTM) - self.saveNWD = QAction("novelWriter Markdown (.nwd)", self) + self.saveNWD = QAction(self.tr("{0} ({1})").format( + self.tr("novelWriter Markdown"), ".nwd"), self) self.saveNWD.triggered.connect(lambda: self._saveDocument(self.FMT_NWD)) self.saveMenu.addAction(self.saveNWD) - self.saveMD = QAction("Standard Markdown (.md)", self) + self.saveMD = QAction( + self.tr("{0} ({1})").format(self.tr("Standard Markdown"), ".md"), self) self.saveMD.triggered.connect(lambda: self._saveDocument(self.FMT_MD)) self.saveMenu.addAction(self.saveMD) - self.saveGH = QAction("GitHub Markdown (.md)", self) + self.saveGH = QAction( + self.tr("{0} ({1})").format(self.tr("GitHub Markdown"), ".md"), self) self.saveGH.triggered.connect(lambda: self._saveDocument(self.FMT_GH)) self.saveMenu.addAction(self.saveGH) - self.saveJsonH = QAction("JSON + novelWriter HTML (.json)", self) + self.saveJsonH = QAction(self.tr("{0} ({1})").format( + self.tr("JSON + novelWriter HTML"), ".json"), self) self.saveJsonH.triggered.connect(lambda: self._saveDocument(self.FMT_JSON_H)) self.saveMenu.addAction(self.saveJsonH) - self.saveJsonM = QAction("JSON + novelWriters Markdown (.json)", self) + self.saveJsonM = QAction(self.tr("{0} ({1})").format( + self.tr("JSON + novelWriters Markdown"), ".json"), self) self.saveJsonM.triggered.connect(lambda: self._saveDocument(self.FMT_JSON_M)) self.saveMenu.addAction(self.saveJsonM) - self.btnClose = QPushButton("Close") + self.btnClose = QPushButton(self.tr("Close")) self.btnClose.clicked.connect(self._doClose) self.buttonBox.addWidget(self.btnSave) @@ -567,7 +574,7 @@ class GuiBuildNovel(QDialog): self.docView.setContent(self.htmlText, self.buildTime) else: self.docView.setText( - "Failed to generate preview. The result is too big." + self.tr("Failed to generate preview. The result is too big.") ) else: @@ -736,10 +743,9 @@ class GuiBuildNovel(QDialog): logger.debug("Built project in %.3f ms" % (1000*(tEnd - tStart))) if bldObj.errData: - self.theParent.makeAlert(( - "There were problems when building the project:" - "
- %s" - ) % "
- ".join(bldObj.errData), nwAlert.ERROR) + self.theParent.makeAlert("%s:
- %s" % ( + self.tr("There were problems when building the project"), + "
- ".join(bldObj.errData)), nwAlert.ERROR) return @@ -796,39 +802,39 @@ class GuiBuildNovel(QDialog): if theFmt == self.FMT_ODT: fileExt = "odt" - textFmt = "Open Document" + textFmt = self.tr("Open Document") elif theFmt == self.FMT_FODT: fileExt = "fodt" - textFmt = "Flat Open Document" + textFmt = self.tr("Flat Open Document") elif theFmt == self.FMT_HTM: fileExt = "htm" - textFmt = "Plain HTML" + textFmt = self.tr("Plain HTML") elif theFmt == self.FMT_NWD: fileExt = "nwd" - textFmt = "novelWriter Markdown" + textFmt = self.tr("novelWriter Markdown") elif theFmt == self.FMT_MD: fileExt = "md" - textFmt = "Standard Markdown" + textFmt = self.tr("Standard Markdown") elif theFmt == self.FMT_GH: fileExt = "md" - textFmt = "GitHub Markdown" + textFmt = self.tr("GitHub Markdown") elif theFmt == self.FMT_JSON_H: fileExt = "json" - textFmt = "JSON + novelWriter HTML" + textFmt = self.tr("JSON + novelWriter HTML") elif theFmt == self.FMT_JSON_M: fileExt = "json" - textFmt = "JSON + novelWriter Markdown" + textFmt = self.tr("JSON + novelWriter Markdown") elif theFmt == self.FMT_PDF: fileExt = "pdf" - textFmt = "PDF" + textFmt = self.tr("PDF") else: return False @@ -848,7 +854,7 @@ class GuiBuildNovel(QDialog): dlgOpt = QFileDialog.Options() dlgOpt |= QFileDialog.DontUseNativeDialog savePath, _ = QFileDialog.getSaveFileName( - self, "Save Document As", savePath, options=dlgOpt + self, self.tr("Save Document As"), savePath, options=dlgOpt ) if not savePath: return False @@ -985,20 +991,20 @@ class GuiBuildNovel(QDialog): errMsg - str(e) else: - errMsg = "Unknown format" + errMsg = self.tr("Unknown format") # Report to user if wSuccess: self.theParent.makeAlert( - "%s file successfully written to:
%s" % ( - textFmt, savePath + "%s
%s" % ( + self.tr("{0} file successfully written to:").format(textFmt), + savePath ), nwAlert.INFO ) else: self.theParent.makeAlert( - "Failed to write %s file. %s" % ( - textFmt, errMsg - ), nwAlert.ERROR + self.tr("Failed to write {0} file. {1}").format( + textFmt, errMsg), nwAlert.ERROR ) return wSuccess @@ -1194,9 +1200,9 @@ class GuiBuildNovelDocView(QTextBrowser): self.qDocument = self.document() self.qDocument.setDocumentMargin(self.mainConf.getTextMargin()) self.setPlaceholderText( - "This area will show the content of the document to be " - "exported or printed. Press the \"Build Preview\" button " - "to generate content." + self.tr("This area will show the content of the document to be " + "exported or printed. Press the \"Build Preview\" button " + "to generate content.") ) theFont = QFont() @@ -1227,7 +1233,8 @@ class GuiBuildNovelDocView(QTextBrowser): fPx = int(1.1*self.theTheme.fontPixelSize) - self.theTitle = QLabel("Build Time: Unknown", self) + self.theTitle = QLabel(self.tr("{0}: {1}".format( + self.tr("Build Time"), self.tr("Unknown"))), self) self.theTitle.setIndent(0) self.theTitle.setAutoFillBackground(True) self.theTitle.setAlignment(Qt.AlignCenter) @@ -1341,8 +1348,9 @@ class GuiBuildNovelDocView(QTextBrowser): fuzzyTime(time() - self.buildTime) ) else: - strBuildTime = "Unknown" - self.theTitle.setText("Build Time: %s" % strBuildTime) + strBuildTime = self.tr("Unknown") + self.theTitle.setText(self.tr("{0}: {1}").format( + self.tr("Build Time"), strBuildTime)) def _updateDocMargins(self): """Automatically adjust the header to fill the top of the diff --git a/nw/gui/custom.py b/nw/gui/custom.py index 303a5ae1..4b187f21 100644 --- a/nw/gui/custom.py +++ b/nw/gui/custom.py @@ -500,6 +500,8 @@ class QuotesDialog(QDialog): # Buttons self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("Ok")) + self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel")) self.buttonBox.accepted.connect(self._doAccept) self.buttonBox.rejected.connect(self._doReject) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index abafc6fb..146fe07e 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -36,7 +36,7 @@ import logging from time import time from PyQt5.QtCore import ( - Qt, QSize, QTimer, pyqtSlot, pyqtSignal, QRegExp, QRegularExpression, + QCoreApplication, Qt, QSize, QTimer, pyqtSlot, pyqtSignal, QRegExp, QRegularExpression, QPointF, QObject, QRunnable, QPropertyAnimation ) from PyQt5.QtGui import ( @@ -294,10 +294,14 @@ class GuiDocEditor(QTextEdit): docSize = len(theDoc) if docSize > nwConst.MAX_DOCSIZE: self.theParent.makeAlert(( - "The document you are trying to open is too big. " - "The document size is %.2f\u202fMB. " - "The maximum size allowed is %.2f\u202fMB." - ) % (docSize/1.0e6, nwConst.MAX_DOCSIZE/1.0e6), nwAlert.ERROR) + self.tr("The document you are trying to open is too big. " + "The document size is {doc_size}. " + "The maximum size allowed is {max_size}."). + format( + doc_size=self.tr("{0}\u202fMB").format(f"{docSize/1.0e6:.2f}"), + max_size=self.tr("{0}\u202fMB").format(f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}") + ) + ), nwAlert.ERROR) self.clearEditor() return False @@ -381,10 +385,14 @@ class GuiDocEditor(QTextEdit): docSize = len(theText) if docSize > nwConst.MAX_DOCSIZE: self.theParent.makeAlert(( - "The text you are trying to add is too big. " - "The text size is %.2f\u202fMB. " - "The maximum size allowed is %.2f\u202fMB." - ) % (docSize/1.0e6, nwConst.MAX_DOCSIZE/1.0e6), nwAlert.ERROR) + self.tr("The text you are trying to add is too big. " + "The text size is {text_size}. " + "The maximum size allowed is {max_size}."). + format( + text_size=self.tr("{0}\u202fMB").format(f"{docSize/1.0e6:.2f}"), + max_size=self.tr("{0}\u202fMB").format(f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}") + ) + ), nwAlert.ERROR) return False qApp.setOverrideCursor(QCursor(Qt.WaitCursor)) @@ -594,7 +602,10 @@ class GuiDocEditor(QTextEdit): aLang, aName = self.theDict.describeDict() self.theParent.statusBar.setLanguage( - aLang, "%s [%s]" % (self.mainConf.spellTool.title(), aName.title()) + aLang, + self.tr("{0} [{1}]").format( + self.mainConf.spellTool.title(), + aName.title()) ) if not self.bigDoc: @@ -644,7 +655,7 @@ class GuiDocEditor(QTextEdit): logger.debug( "Document highlighted in %.3f ms" % (1000*(afTime-bfTime)) ) - self.theParent.statusBar.showMessage("Spell check complete") + self.theParent.statusBar.showMessage(self.tr("Spell check complete")) return True @@ -743,11 +754,12 @@ class GuiDocEditor(QTextEdit): return False msgBox = QMessageBox() - msgBox.information(self, "File Location", ( - "File details for the currently open file
" - "Handle: {handle:s}
" - "Location: {fileLoc:s}" - ).format( + msgBox.information(self, self.tr("File Location"), "".join([ + (self.tr("{0}
").format(self.tr("File details for the currently open file"))), + (self.tr("{0}
").format( + self.tr("{0}: {1}").format(self.tr("Handle"), "{handle:s}"))), + (self.tr("{0}: {1}").format(self.tr("Location"), "{fileLoc:s}")) + ]).format( handle = self.theHandle, fileLoc = str(self.nwDocument.getFileLocation()) )) @@ -934,9 +946,10 @@ class GuiDocEditor(QTextEdit): if self.qDocument.characterCount() > nwConst.MAX_DOCSIZE: self.theParent.makeAlert(( - "The document has grown too big and you cannot add more text to it. " - "The maximum size of a single novelWriter document is %.2f\u202fMB." - ) % (nwConst.MAX_DOCSIZE/1.0e6), nwAlert.ERROR) + self.tr("The document has grown too big and you cannot add more text to it. " + "The maximum size of a single novelWriter document is {max_size}."). + format(max_size=self.tr("{0}\u202fMB").format(f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}")) + ), nwAlert.ERROR) self.undo() return @@ -966,21 +979,21 @@ class GuiDocEditor(QTextEdit): # =========================== if self._followTag(theCursor=posCursor, loadTag=False): - mnuTag = QAction("Follow Tag", mnuContext) + mnuTag = QAction(self.tr("Follow Tag"), mnuContext) mnuTag.triggered.connect(lambda: self._followTag(theCursor=posCursor)) mnuContext.addAction(mnuTag) mnuContext.addSeparator() if userSelection: - mnuCut = QAction("Cut", mnuContext) + mnuCut = QAction(self.tr("Cut"), mnuContext) mnuCut.triggered.connect(lambda: self.docAction(nwDocAction.CUT)) mnuContext.addAction(mnuCut) - mnuCopy = QAction("Copy", mnuContext) + mnuCopy = QAction(self.tr("Copy"), mnuContext) mnuCopy.triggered.connect(lambda: self.docAction(nwDocAction.COPY)) mnuContext.addAction(mnuCopy) - mnuPaste = QAction("Paste", mnuContext) + mnuPaste = QAction(self.tr("Paste"), mnuContext) mnuPaste.triggered.connect(lambda: self.docAction(nwDocAction.PASTE)) mnuContext.addAction(mnuPaste) @@ -989,17 +1002,17 @@ class GuiDocEditor(QTextEdit): # Selections # ========== - mnuSelAll = QAction("Select All", mnuContext) + mnuSelAll = QAction(self.tr("Select All"), mnuContext) mnuSelAll.triggered.connect(lambda: self.docAction(nwDocAction.SEL_ALL)) mnuContext.addAction(mnuSelAll) - mnuSelWord = QAction("Select Word", mnuContext) + mnuSelWord = QAction(self.tr("Select Word"), mnuContext) mnuSelWord.triggered.connect( lambda: self._makePosSelection(QTextCursor.WordUnderCursor, thePos) ) mnuContext.addAction(mnuSelWord) - mnuSelPara = QAction("Select Paragraph", mnuContext) + mnuSelPara = QAction(self.tr("Select Paragraph"), mnuContext) mnuSelPara.triggered.connect( lambda: self._makePosSelection(QTextCursor.BlockUnderCursor, thePos) ) @@ -1025,7 +1038,7 @@ class GuiDocEditor(QTextEdit): if spellCheck: mnuContext.addSeparator() - mnuHead = QAction("Spelling Suggestion(s)", mnuContext) + mnuHead = QAction(self.tr("Spelling Suggestion(s)"), mnuContext) mnuContext.addAction(mnuHead) theSuggest = self.theDict.suggestWords(theWord)[:15] @@ -1037,11 +1050,12 @@ class GuiDocEditor(QTextEdit): ) mnuContext.addAction(mnuWord) else: - mnuHead = QAction("%s No Suggestions" % nwUnicode.U_ENDASH, mnuContext) + mnuHead = QAction("%s %s" % (nwUnicode.U_ENDASH, self.tr("No Suggestions")), + mnuContext) mnuContext.addAction(mnuHead) mnuContext.addSeparator() - mnuAdd = QAction("Add Word to Dictionary", mnuContext) + mnuAdd = QAction(self.tr("Add Word to Dictionary"), mnuContext) mnuAdd.triggered.connect(lambda thePos: self._addWord(posCursor)) mnuContext.addAction(mnuAdd) @@ -1317,7 +1331,8 @@ class GuiDocEditor(QTextEdit): else: self.theParent.makeAlert( - "Please selection some text before calling replace quotes.", nwAlert.ERROR + self.tr("Please selection some text before calling replace quotes."), + nwAlert.ERROR ) return @@ -1817,12 +1832,12 @@ class GuiDocEditSearch(QFrame): # ========== self.searchBox = QLineEdit(self) self.searchBox.setFont(boxFont) - self.searchBox.setPlaceholderText("Search") + self.searchBox.setPlaceholderText(self.tr("Search")) self.searchBox.returnPressed.connect(self._doSearch) self.replaceBox = QLineEdit(self) self.replaceBox.setFont(boxFont) - self.replaceBox.setPlaceholderText("Replace") + self.replaceBox.setPlaceholderText(self.tr("Replace")) self.replaceBox.returnPressed.connect(self._doReplace) self.searchOpt = QToolBar(self) @@ -1831,44 +1846,45 @@ class GuiDocEditSearch(QFrame): self.searchOpt.setContentsMargins(0, 0, 0, 0) self.searchOpt.setStyleSheet(r"QToolBar {padding: 0;}") - self.searchLabel = QLabel("Search") + self.searchLabel = QLabel(self.tr("Search")) self.searchLabel.setFont(boxFont) self.searchLabel.setIndent(self.mainConf.pxInt(6)) - self.toggleCase = QAction("Case Sensitive", self) - self.toggleCase.setToolTip("Match case") + self.toggleCase = QAction(self.tr("Case Sensitive"), self) + self.toggleCase.setToolTip(self.tr("Match case")) self.toggleCase.setIcon(self.theTheme.getIcon("search_case")) self.toggleCase.setCheckable(True) self.toggleCase.setChecked(self.isCaseSense) self.toggleCase.toggled.connect(self._doToggleCase) self.searchOpt.addAction(self.toggleCase) - self.toggleWord = QAction("Whole Words Only", self) - self.toggleWord.setToolTip("Match whole words") + self.toggleWord = QAction(self.tr("Whole Words Only"), self) + self.toggleWord.setToolTip(self.tr("Match whole words")) self.toggleWord.setIcon(self.theTheme.getIcon("search_word")) self.toggleWord.setCheckable(True) self.toggleWord.setChecked(self.isWholeWord) self.toggleWord.toggled.connect(self._doToggleWord) self.searchOpt.addAction(self.toggleWord) - self.toggleRegEx = QAction("RegEx Mode", self) - self.toggleRegEx.setToolTip("Use regular expressions (requires Qt 5.3)") + self.toggleRegEx = QAction(self.tr("RegEx Mode"), self) + self.toggleRegEx.setToolTip(self.tr("Use regular expressions (requires Qt {0})").format( + "5.3")) self.toggleRegEx.setIcon(self.theTheme.getIcon("search_regex")) self.toggleRegEx.setCheckable(True) self.toggleRegEx.setChecked(self.isRegEx) self.toggleRegEx.toggled.connect(self._doToggleRegEx) self.searchOpt.addAction(self.toggleRegEx) - self.toggleLoop = QAction("Loop Search", self) - self.toggleLoop.setToolTip("Loop the search when reaching the end") + self.toggleLoop = QAction(self.tr("Loop Search"), self) + self.toggleLoop.setToolTip(self.tr("Loop the search when reaching the end")) self.toggleLoop.setIcon(self.theTheme.getIcon("search_loop")) self.toggleLoop.setCheckable(True) self.toggleLoop.setChecked(self.doLoop) self.toggleLoop.toggled.connect(self._doToggleLoop) self.searchOpt.addAction(self.toggleLoop) - self.toggleProject = QAction("Search Next File", self) - self.toggleProject.setToolTip("Continue searching in the next file") + self.toggleProject = QAction(self.tr("Search Next File"), self) + self.toggleProject.setToolTip(self.tr("Continue searching in the next file")) self.toggleProject.setIcon(self.theTheme.getIcon("search_project")) self.toggleProject.setCheckable(True) self.toggleProject.setChecked(self.doNextFile) @@ -1877,8 +1893,8 @@ class GuiDocEditSearch(QFrame): self.searchOpt.addSeparator() - self.toggleMatchCap = QAction("Preserve Case", self) - self.toggleMatchCap.setToolTip("Preserve case on replace") + self.toggleMatchCap = QAction(self.tr("Preserve Case"), self) + self.toggleMatchCap.setToolTip(self.tr("Preserve case on replace")) self.toggleMatchCap.setIcon(self.theTheme.getIcon("search_preserve")) self.toggleMatchCap.setCheckable(True) self.toggleMatchCap.setChecked(self.doMatchCap) @@ -1887,8 +1903,8 @@ class GuiDocEditSearch(QFrame): self.searchOpt.addSeparator() - self.cancelSearch = QAction("Close Search", self) - self.cancelSearch.setToolTip("Close the search box [Esc]") + self.cancelSearch = QAction(self.tr("Close Search"), self) + self.cancelSearch.setToolTip(self.tr("Close the search box [{0}]").format("Esc")) self.cancelSearch.setIcon(self.theTheme.getIcon("search_cancel")) self.cancelSearch.triggered.connect(self._doClose) self.searchOpt.addAction(self.cancelSearch) @@ -1900,18 +1916,18 @@ class GuiDocEditSearch(QFrame): self.showReplace = QToolButton(self) self.showReplace.setArrowType(Qt.RightArrow) self.showReplace.setCheckable(True) - self.showReplace.setToolTip("Show/hide the replace text box") + self.showReplace.setToolTip(self.tr("Show/hide the replace text box")) self.showReplace.setStyleSheet(r"QToolButton {border: none; background: transparent;}") self.showReplace.toggled.connect(self._doToggleReplace) self.searchButton = QPushButton(self.theTheme.getIcon("search"), "") self.searchButton.setFixedSize(QSize(bPx, bPx)) - self.searchButton.setToolTip("Find in current document") + self.searchButton.setToolTip(self.tr("Find in current document")) self.searchButton.clicked.connect(self._doSearch) self.replaceButton = QPushButton(self.theTheme.getIcon("search-replace"), "") self.replaceButton.setFixedSize(QSize(bPx, bPx)) - self.replaceButton.setToolTip("Find and replace in current document") + self.replaceButton.setToolTip(self.tr("Find and replace in current document")) self.replaceButton.clicked.connect(self._doReplace) self.mainBox.addWidget(self.searchLabel, 0, 0, 1, 2, Qt.AlignLeft) @@ -2204,7 +2220,7 @@ class GuiDocEditHeader(QWidget): self.editButton.setStyleSheet(buttonStyle) self.editButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.editButton.setVisible(False) - self.editButton.setToolTip("Edit document meta") + self.editButton.setToolTip(self.tr("Edit document meta")) self.editButton.clicked.connect(self._editDocument) self.searchButton = QToolButton(self) @@ -2215,7 +2231,7 @@ class GuiDocEditHeader(QWidget): self.searchButton.setStyleSheet(buttonStyle) self.searchButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.searchButton.setVisible(False) - self.searchButton.setToolTip("Search document") + self.searchButton.setToolTip(self.tr("Search document")) self.searchButton.clicked.connect(self._searchDocument) self.minmaxButton = QToolButton(self) @@ -2226,7 +2242,7 @@ class GuiDocEditHeader(QWidget): self.minmaxButton.setStyleSheet(buttonStyle) self.minmaxButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.minmaxButton.setVisible(False) - self.minmaxButton.setToolTip("Toggle Focus Mode") + self.minmaxButton.setToolTip(self.tr("Toggle Focus Mode")) self.minmaxButton.clicked.connect(self._minmaxDocument) self.closeButton = QToolButton(self) @@ -2237,7 +2253,7 @@ class GuiDocEditHeader(QWidget): self.closeButton.setStyleSheet(buttonStyle) self.closeButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.closeButton.setVisible(False) - self.closeButton.setToolTip("Close the document") + self.closeButton.setToolTip(self.tr("Close the document")) self.closeButton.clicked.connect(self._closeDocument) # Assemble Layout @@ -2413,7 +2429,7 @@ class GuiDocEditFooter(QWidget): self.statusIcon.setFixedHeight(self.sPx) self.statusIcon.setAlignment(Qt.AlignLeft | Qt.AlignTop) - self.statusText = QLabel("Status") + self.statusText = QLabel(self.tr("Status")) self.statusText.setIndent(0) self.statusText.setMargin(0) self.statusText.setContentsMargins(0, 0, 0, 0) @@ -2429,7 +2445,7 @@ class GuiDocEditFooter(QWidget): self.linesIcon.setFixedHeight(self.sPx) self.linesIcon.setAlignment(Qt.AlignLeft | Qt.AlignTop) - self.linesText = QLabel("Line: 0") + self.linesText = QLabel(self.tr("{0}: {1}").format(self.tr("Line"), "0")) self.linesText.setIndent(0) self.linesText.setMargin(0) self.linesText.setContentsMargins(0, 0, 0, 0) @@ -2445,7 +2461,7 @@ class GuiDocEditFooter(QWidget): self.wordsIcon.setFixedHeight(self.sPx) self.wordsIcon.setAlignment(Qt.AlignLeft | Qt.AlignTop) - self.wordsText = QLabel("Words: 0") + self.wordsText = QLabel(self.tr("{0}: {1}").format(self.tr("Words"), "0")) self.wordsText.setIndent(0) self.wordsText.setMargin(0) self.wordsText.setContentsMargins(0, 0, 0, 0) @@ -2532,8 +2548,10 @@ class GuiDocEditFooter(QWidget): theIcon = self.theParent.importIcons[iStatus] sIcon = theIcon.pixmap(self.sPx, self.sPx) - sClass = nwLabels.CLASS_NAME[self.theItem.itemClass] - sLayout = nwLabels.LAYOUT_NAME[self.theItem.itemLayout] + sClass = QCoreApplication.translate( + "Constant", nwLabels.CLASS_NAME[self.theItem.itemClass]) + sLayout = QCoreApplication.translate( + "Constant", nwLabels.LAYOUT_NAME[self.theItem.itemLayout]) sText = f"{self.theItem.itemStatus} / {sClass} / {sLayout}" self.statusIcon.setPixmap(sIcon) @@ -2552,7 +2570,9 @@ class GuiDocEditFooter(QWidget): iLine = theCursor.blockNumber() + 1 iDist = 100*iLine/self.docEditor.qDocument.blockCount() - self.linesText.setText(f"Line: {iLine:n} ({iDist:.0f}\u202f%)") + self.linesText.setText( + self.tr("{0}: {1} ({2}\u202f%%)".format( + self.tr("Line"), f"{iLine:n}", f"{iDist:.0f}"))) return @@ -2566,10 +2586,13 @@ class GuiDocEditFooter(QWidget): wCount = self.theItem.wordCount wDiff = wCount - self.theItem.initCount - self.wordsText.setText(f"Words: {wCount:n} ({wDiff:+n})") + self.wordsText.setText( + self.tr("{0}: {1} ({2})".format( + self.tr("Words"), f"{wCount:n}", f"{wDiff:+n}"))) byteSize = self.docEditor.qDocument.characterCount() - self.wordsText.setToolTip(f"Document size is {byteSize:n} bytes") + self.wordsText.setToolTip( + (self.tr("Document size is {0} bytes").format(f"{byteSize:n}"))) return diff --git a/nw/gui/docmerge.py b/nw/gui/docmerge.py index d3004ec1..c62bd5d1 100644 --- a/nw/gui/docmerge.py +++ b/nw/gui/docmerge.py @@ -53,11 +53,11 @@ class GuiDocMerge(QDialog): self.sourceItem = None self.outerBox = QVBoxLayout() - self.setWindowTitle("Merge Documents") + self.setWindowTitle(self.tr("Merge Documents")) - self.headLabel = QLabel("Documents to Merge") + self.headLabel = QLabel("%s" % self.tr("Documents to Merge")) self.helpLabel = QHelpLabel( - "Drag and drop items to change the order.", self.theParent.theTheme.helpText + self.tr("Drag and drop items to change the order."), self.theParent.theTheme.helpText ) self.listBox = QListWidget() @@ -66,6 +66,8 @@ class GuiDocMerge(QDialog): self.listBox.setMinimumHeight(self.mainConf.pxInt(180)) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("Ok")) + self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel")) self.buttonBox.accepted.connect(self._doMerge) self.buttonBox.rejected.connect(self._doClose) @@ -103,7 +105,7 @@ class GuiDocMerge(QDialog): if len(finalOrder) == 0: self.theParent.makeAlert(( - "No source documents found. Nothing to do." + self.tr("No source documents found. Nothing to do.") ), nwAlert.ERROR) return @@ -115,14 +117,14 @@ class GuiDocMerge(QDialog): if self.sourceItem is None: self.theParent.makeAlert(( - "No source document selected. Nothing to do." + self.tr("No source document selected. Nothing to do.") ), nwAlert.ERROR) return srcItem = self.theProject.projTree[self.sourceItem] if srcItem is None: self.theParent.makeAlert(( - "Could not parse source document." + self.tr("Could not parse source document.") ), nwAlert.ERROR) return @@ -165,7 +167,7 @@ class GuiDocMerge(QDialog): return if nwItem.itemType is not nwItemType.FOLDER: self.theParent.makeAlert(( - "Element selected in the project tree must be a folder." + self.tr("Element selected in the project tree must be a folder.") ), nwAlert.ERROR) return diff --git a/nw/gui/docsplit.py b/nw/gui/docsplit.py index a06aaa67..ae611b4b 100644 --- a/nw/gui/docsplit.py +++ b/nw/gui/docsplit.py @@ -56,11 +56,12 @@ class GuiDocSplit(QDialog): self.sourceItem = None self.outerBox = QVBoxLayout() - self.setWindowTitle("Split Document") + self.setWindowTitle(self.tr("Split Document")) - self.headLabel = QLabel("Document Headers") + self.headLabel = QLabel("%s" % self.tr("Document Headers")) self.helpLabel = QHelpLabel( - "Select the maximum level to split into files.", self.theParent.theTheme.helpText + self.tr("Select the maximum level to split into files."), + self.theParent.theTheme.helpText ) self.listBox = QListWidget() @@ -69,10 +70,10 @@ class GuiDocSplit(QDialog): self.listBox.setMinimumHeight(self.mainConf.pxInt(180)) self.splitLevel = QComboBox(self) - self.splitLevel.addItem("Split on Header Level 1 (Title)", 1) - self.splitLevel.addItem("Split up to Header Level 2 (Chapter)", 2) - self.splitLevel.addItem("Split up to Header Level 3 (Scene)", 3) - self.splitLevel.addItem("Split up to Header Level 4 (Section)", 4) + self.splitLevel.addItem(self.tr("Split on Header Level 1 (Title)"), 1) + self.splitLevel.addItem(self.tr("Split up to Header Level 2 (Chapter)"), 2) + self.splitLevel.addItem(self.tr("Split up to Header Level 3 (Scene)"), 3) + self.splitLevel.addItem(self.tr("Split up to Header Level 4 (Section)"), 4) spIndex = self.splitLevel.findData( self.optState.getInt("GuiDocSplit", "spLevel", 3) ) @@ -81,6 +82,8 @@ class GuiDocSplit(QDialog): self.splitLevel.currentIndexChanged.connect(self._populateList) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("Ok")) + self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel")) self.buttonBox.accepted.connect(self._doSplit) self.buttonBox.rejected.connect(self._doClose) @@ -116,14 +119,14 @@ class GuiDocSplit(QDialog): if self.sourceItem is None: self.theParent.makeAlert(( - "No source document selected. Nothing to do." + self.tr("No source document selected. Nothing to do.") ), nwAlert.ERROR) return srcItem = self.theProject.projTree[self.sourceItem] if srcItem is None: self.theParent.makeAlert(( - "Could not parse source document." + self.tr("Could not parse source document.") ), nwAlert.ERROR) return @@ -148,7 +151,7 @@ class GuiDocSplit(QDialog): nFiles = len(finalOrder) if nFiles == 0: self.theParent.makeAlert(( - "No headers found. Nothing to do." + self.tr("No headers found. Nothing to do.") ), nwAlert.ERROR) return @@ -156,17 +159,17 @@ class GuiDocSplit(QDialog): parTree = self.theProject.projTree.getItemPath(srcItem.itemParent) if len(parTree) >= nwConst.MAX_DEPTH - 1: self.theParent.makeAlert(( - "Cannot add new folder for the document split. " - "Maximum folder depth has been reached. " - "Please move the file to another level in the project tree." + self.tr("Cannot add new folder for the document split. " + "Maximum folder depth has been reached. " + "Please move the file to another level in the project tree.") ), nwAlert.ERROR) return - msgYes = self.theParent.askQuestion("Split Document", ( - "The document will be split into %d file(s) in a new folder. " - "The original document will remain intact.

" - "Continue with the splitting process?" - ) % nFiles) + msgYes = self.theParent.askQuestion(self.tr("Split Document"), "%s

%s" % ( + self.tr("The document will be split into {0} file(s) in a new folder. " + "The original document will remain intact.", n=nFiles).format(nFiles), + self.tr("Continue with the splitting process?") + )) if not msgYes: return @@ -243,7 +246,7 @@ class GuiDocSplit(QDialog): return if nwItem.itemType is not nwItemType.FILE: self.theParent.makeAlert(( - "Element selected in the project tree must be a file." + self.tr("Element selected in the project tree must be a file.") ), nwAlert.ERROR) return diff --git a/nw/gui/docviewer.py b/nw/gui/docviewer.py index ddc0d876..0b34ebeb 100644 --- a/nw/gui/docviewer.py +++ b/nw/gui/docviewer.py @@ -183,7 +183,7 @@ class GuiDocViewer(QTextBrowser): except Exception: logger.error("Failed to generate preview for document with handle '%s'" % tHandle) nw.logException() - self.setText("An error occurred while generating the preview.") + self.setText(self.tr("An error occurred while generating the preview.")) return False # Refresh the tab stops @@ -243,11 +243,11 @@ class GuiDocViewer(QTextBrowser): logger.debug("Loading document from tag '%s'" % theTag) tHandle, _, sTitle = self.theParent.theIndex.getTagSource(theTag) if tHandle is None: - self.theParent.makeAlert(( - "Could not find the reference for tag '%s'. It either doesn't " - "exist, or the index is out of date. The index can be updated " - "from the Tools menu, or by pressing F9." - ) % theTag, nwAlert.ERROR) + self.theParent.makeAlert( + self.tr("Could not find the reference for tag '{0}'. It either doesn't " + "exist, or the index is out of date. The index can be updated " + "from the Tools menu, or by pressing {1}."). + format(theTag, "F9"), nwAlert.ERROR) return False else: # Let the parent handle the opening as it also ensures that @@ -415,7 +415,7 @@ class GuiDocViewer(QTextBrowser): # =================== if userSelection: - mnuCopy = QAction("Copy", mnuContext) + mnuCopy = QAction(self.tr("Copy"), mnuContext) mnuCopy.triggered.connect(lambda: self.docAction(nwDocAction.COPY)) mnuContext.addAction(mnuCopy) @@ -424,17 +424,17 @@ class GuiDocViewer(QTextBrowser): # Selections # ========== - mnuSelAll = QAction("Select All", mnuContext) + mnuSelAll = QAction(self.tr("Select All"), mnuContext) mnuSelAll.triggered.connect(lambda: self.docAction(nwDocAction.SEL_ALL)) mnuContext.addAction(mnuSelAll) - mnuSelWord = QAction("Select Word", mnuContext) + mnuSelWord = QAction(self.tr("Select Word"), mnuContext) mnuSelWord.triggered.connect( lambda: self._makePosSelection(QTextCursor.WordUnderCursor, thePos) ) mnuContext.addAction(mnuSelWord) - mnuSelPara = QAction("Select Paragraph", mnuContext) + mnuSelPara = QAction(self.tr("Select Paragraph"), mnuContext) mnuSelPara.triggered.connect( lambda: self._makePosSelection(QTextCursor.BlockUnderCursor, thePos) ) @@ -740,7 +740,7 @@ class GuiDocViewHeader(QWidget): self.backButton.setStyleSheet(buttonStyle) self.backButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.backButton.setVisible(False) - self.backButton.setToolTip("Go backward") + self.backButton.setToolTip(self.tr("Go backward")) self.backButton.clicked.connect(self.docViewer.navBackward) self.forwardButton = QToolButton(self) @@ -751,7 +751,7 @@ class GuiDocViewHeader(QWidget): self.forwardButton.setStyleSheet(buttonStyle) self.forwardButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.forwardButton.setVisible(False) - self.forwardButton.setToolTip("Go forward") + self.forwardButton.setToolTip(self.tr("Go forward")) self.forwardButton.clicked.connect(self.docViewer.navForward) self.refreshButton = QToolButton(self) @@ -762,7 +762,7 @@ class GuiDocViewHeader(QWidget): self.refreshButton.setStyleSheet(buttonStyle) self.refreshButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.refreshButton.setVisible(False) - self.refreshButton.setToolTip("Reload the document") + self.refreshButton.setToolTip(self.tr("Reload the document")) self.refreshButton.clicked.connect(self._refreshDocument) self.closeButton = QToolButton(self) @@ -773,7 +773,7 @@ class GuiDocViewHeader(QWidget): self.closeButton.setStyleSheet(buttonStyle) self.closeButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.closeButton.setVisible(False) - self.closeButton.setToolTip("Close the document") + self.closeButton.setToolTip(self.tr("Close the document")) self.closeButton.clicked.connect(self._closeDocument) # Assemble Layout @@ -944,7 +944,7 @@ class GuiDocViewFooter(QWidget): self.showHide.setIconSize(QSize(fPx, fPx)) self.showHide.setFixedSize(QSize(fPx, fPx)) self.showHide.clicked.connect(self._doShowHide) - self.showHide.setToolTip("Show/hide the references panel") + self.showHide.setToolTip(self.tr("Show/hide the references panel")) # Sticky Button self.stickyRefs = QToolButton(self) @@ -956,7 +956,8 @@ class GuiDocViewFooter(QWidget): self.stickyRefs.setFixedSize(QSize(fPx, fPx)) self.stickyRefs.toggled.connect(self._doToggleSticky) self.stickyRefs.setToolTip( - "Activate to freeze the content of the references panel when changing document" + self.tr("Activate to freeze the content of the references panel when " + "changing document") ) # Show Comments @@ -969,7 +970,7 @@ class GuiDocViewFooter(QWidget): self.showComments.setIconSize(QSize(fPx, fPx)) self.showComments.setFixedSize(QSize(fPx, fPx)) self.showComments.toggled.connect(self._doToggleComments) - self.showComments.setToolTip("Show comments") + self.showComments.setToolTip(self.tr("Show comments")) # Show Synopsis self.showSynopsis = QToolButton(self) @@ -981,10 +982,10 @@ class GuiDocViewFooter(QWidget): self.showSynopsis.setIconSize(QSize(fPx, fPx)) self.showSynopsis.setFixedSize(QSize(fPx, fPx)) self.showSynopsis.toggled.connect(self._doToggleSynopsis) - self.showSynopsis.setToolTip("Show synopsis comments") + self.showSynopsis.setToolTip(self.tr("Show synopsis comments")) # Labels - self.lblRefs = QLabel("References") + self.lblRefs = QLabel(self.tr("References")) self.lblRefs.setBuddy(self.showHide) self.lblRefs.setIndent(0) self.lblRefs.setMargin(0) @@ -993,7 +994,7 @@ class GuiDocViewFooter(QWidget): self.lblRefs.setFixedHeight(fPx) self.lblRefs.setAlignment(Qt.AlignLeft | Qt.AlignTop) - self.lblSticky = QLabel("Sticky") + self.lblSticky = QLabel(self.tr("Sticky")) self.lblSticky.setBuddy(self.stickyRefs) self.lblSticky.setIndent(0) self.lblSticky.setMargin(0) @@ -1002,7 +1003,7 @@ class GuiDocViewFooter(QWidget): self.lblSticky.setFixedHeight(fPx) self.lblSticky.setAlignment(Qt.AlignLeft | Qt.AlignTop) - self.lblComments = QLabel("Comments") + self.lblComments = QLabel(self.tr("Comments")) self.lblComments.setBuddy(self.showComments) self.lblComments.setIndent(0) self.lblComments.setMargin(0) @@ -1011,7 +1012,7 @@ class GuiDocViewFooter(QWidget): self.lblComments.setFixedHeight(fPx) self.lblComments.setAlignment(Qt.AlignLeft | Qt.AlignTop) - self.lblSynopsis = QLabel("Synopsis") + self.lblSynopsis = QLabel(self.tr("Synopsis")) self.lblSynopsis.setBuddy(self.showSynopsis) self.lblSynopsis.setIndent(0) self.lblSynopsis.setMargin(0) diff --git a/nw/gui/itemdetails.py b/nw/gui/itemdetails.py index 52584d40..ec77b2c9 100644 --- a/nw/gui/itemdetails.py +++ b/nw/gui/itemdetails.py @@ -27,7 +27,7 @@ along with this program. If not, see . import nw import logging -from PyQt5.QtCore import Qt +from PyQt5.QtCore import QCoreApplication, Qt from PyQt5.QtGui import QFont, QPixmap from PyQt5.QtWidgets import QWidget, QGridLayout, QLabel @@ -67,7 +67,7 @@ class GuiItemDetails(QWidget): self.fntValue.setPointSizeF(0.9*fPt) # Label - self.labelName = QLabel("Label") + self.labelName = QLabel(self.tr("Label")) self.labelName.setFont(self.fntLabel) self.labelName.setAlignment(Qt.AlignLeft | Qt.AlignBaseline) @@ -80,7 +80,7 @@ class GuiItemDetails(QWidget): self.labelData.setWordWrap(True) # Status - self.statusName = QLabel("Status") + self.statusName = QLabel(self.tr("Status")) self.statusName.setFont(self.fntLabel) self.statusName.setAlignment(Qt.AlignLeft) @@ -92,7 +92,7 @@ class GuiItemDetails(QWidget): self.statusData.setAlignment(Qt.AlignLeft) # Class - self.className = QLabel("Class") + self.className = QLabel(self.tr("Class")) self.className.setFont(self.fntLabel) self.className.setAlignment(Qt.AlignLeft) @@ -105,7 +105,7 @@ class GuiItemDetails(QWidget): self.classData.setAlignment(Qt.AlignLeft) # Layout - self.layoutName = QLabel("Layout") + self.layoutName = QLabel(self.tr("Layout")) self.layoutName.setFont(self.fntLabel) self.layoutName.setAlignment(Qt.AlignLeft) @@ -118,7 +118,7 @@ class GuiItemDetails(QWidget): self.layoutData.setAlignment(Qt.AlignLeft) # Character Count - self.cCountName = QLabel(" Characters") + self.cCountName = QLabel(self.tr(" Characters")) self.cCountName.setFont(self.fntLabel) self.cCountName.setAlignment(Qt.AlignRight) @@ -127,7 +127,7 @@ class GuiItemDetails(QWidget): self.cCountData.setAlignment(Qt.AlignRight) # Word Count - self.wCountName = QLabel(" Words") + self.wCountName = QLabel(self.tr(" Words")) self.wCountName.setFont(self.fntLabel) self.wCountName.setAlignment(Qt.AlignRight) @@ -136,7 +136,7 @@ class GuiItemDetails(QWidget): self.wCountData.setAlignment(Qt.AlignRight) # Paragraph Count - self.pCountName = QLabel(" Paragraphs") + self.pCountName = QLabel(self.tr(" Paragraphs")) self.pCountName.setFont(self.fntLabel) self.pCountName.setAlignment(Qt.AlignRight) @@ -259,17 +259,19 @@ class GuiItemDetails(QWidget): iPx = int(round(0.8*self.theTheme.baseIconSize)) self.statusFlag.setPixmap(flagIcon.pixmap(iPx, iPx)) - self.classFlag.setText(nwLabels.CLASS_FLAG[nwItem.itemClass]) + self.classFlag.setText(nwLabels.CLASS_FLAG[nwItem.itemClass]) # NO-I18N if nwItem.itemLayout == nwItemLayout.NO_LAYOUT: self.layoutFlag.setText("-") else: - self.layoutFlag.setText(nwLabels.LAYOUT_FLAG[nwItem.itemLayout]) + self.layoutFlag.setText(nwLabels.LAYOUT_FLAG[nwItem.itemLayout]) # NO-I18N self.labelData.setText(theLabel) self.statusData.setText(nwItem.itemStatus) - self.classData.setText(nwLabels.CLASS_NAME[nwItem.itemClass]) - self.layoutData.setText(nwLabels.LAYOUT_NAME[nwItem.itemLayout]) + self.classData.setText(QCoreApplication.translate( + "Constant", nwLabels.CLASS_NAME[nwItem.itemClass])) + self.layoutData.setText(QCoreApplication.translate( + "Constant", nwLabels.LAYOUT_NAME[nwItem.itemLayout])) if nwItem.itemType == nwItemType.FILE: self.cCountData.setText(f"{nwItem.charCount:n}") diff --git a/nw/gui/itemeditor.py b/nw/gui/itemeditor.py index 63f67021..37154d86 100644 --- a/nw/gui/itemeditor.py +++ b/nw/gui/itemeditor.py @@ -27,7 +27,7 @@ along with this program. If not, see . import nw import logging -from PyQt5.QtCore import pyqtSlot +from PyQt5.QtCore import QCoreApplication, pyqtSlot from PyQt5.QtWidgets import ( QDialog, QVBoxLayout, QGridLayout, QLineEdit, QComboBox, QLabel, QDialogButtonBox @@ -58,7 +58,7 @@ class GuiItemEditor(QDialog): if self.theItem is None: self._doClose() - self.setWindowTitle("Item Settings") + self.setWindowTitle(self.tr("Item Settings")) mVd = self.mainConf.pxInt(220) mSp = self.mainConf.pxInt(16) @@ -103,10 +103,11 @@ class GuiItemEditor(QDialog): for itemLayout in nwItemLayout: if itemLayout in validLayouts: - self.editLayout.addItem(nwLabels.LAYOUT_NAME[itemLayout], itemLayout) + self.editLayout.addItem(QCoreApplication.translate( + "Constant", nwLabels.LAYOUT_NAME[itemLayout]), itemLayout) # Export Switch - self.textExport = QLabel("Include when building project") + self.textExport = QLabel(self.tr("Include when building project")) self.editExport = QSwitch() if self.theItem.itemType == nwItemType.FILE: self.editExport.setEnabled(True) @@ -117,6 +118,8 @@ class GuiItemEditor(QDialog): # Buttons self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("Ok")) + self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel")) self.buttonBox.accepted.connect(self._doSave) self.buttonBox.rejected.connect(self._doClose) @@ -139,11 +142,11 @@ class GuiItemEditor(QDialog): self.mainForm = QGridLayout() self.mainForm.setVerticalSpacing(vSp) self.mainForm.setHorizontalSpacing(mSp) - self.mainForm.addWidget(QLabel("Label"), 0, 0, 1, 1) + self.mainForm.addWidget(QLabel(self.tr("Label")), 0, 0, 1, 1) self.mainForm.addWidget(self.editName, 0, 1, 1, 2) - self.mainForm.addWidget(QLabel("Status"), 1, 0, 1, 1) + self.mainForm.addWidget(QLabel(self.tr("Status")), 1, 0, 1, 1) self.mainForm.addWidget(self.editStatus, 1, 1, 1, 2) - self.mainForm.addWidget(QLabel("Layout"), 2, 0, 1, 1) + self.mainForm.addWidget(QLabel(self.tr("Layout")), 2, 0, 1, 1) self.mainForm.addWidget(self.editLayout, 2, 1, 1, 2) self.mainForm.addWidget(self.textExport, 3, 0, 1, 2) self.mainForm.addWidget(self.editExport, 3, 2, 1, 1) diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index 6d6267a9..dd2da1bd 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -27,7 +27,7 @@ along with this program. If not, see . import nw import logging -from PyQt5.QtCore import QUrl, QProcess +from PyQt5.QtCore import QCoreApplication, QUrl, QProcess from PyQt5.QtGui import QDesktopServices from PyQt5.QtWidgets import QMenuBar, QAction @@ -180,31 +180,31 @@ class GuiMainMenu(QMenuBar): """Assemble the Project menu. """ # Project - self.projMenu = self.addMenu("&Project") + self.projMenu = self.addMenu(self.tr("&Project")) # Project > New Project - self.aNewProject = QAction("New Project", self) - self.aNewProject.setStatusTip("Create new project") + self.aNewProject = QAction(self.tr("New Project"), self) + self.aNewProject.setStatusTip(self.tr("Create new project")) self.aNewProject.triggered.connect(lambda: self.theParent.newProject(None)) self.projMenu.addAction(self.aNewProject) # Project > Open Project - self.aOpenProject = QAction("Open Project", self) - self.aOpenProject.setStatusTip("Open project") + self.aOpenProject = QAction(self.tr("Open Project"), self) + self.aOpenProject.setStatusTip(self.tr("Open project")) self.aOpenProject.setShortcut("Ctrl+Shift+O") self.aOpenProject.triggered.connect(lambda: self.theParent.showProjectLoadDialog()) self.projMenu.addAction(self.aOpenProject) # Project > Save Project - self.aSaveProject = QAction("Save Project", self) - self.aSaveProject.setStatusTip("Save project") + self.aSaveProject = QAction(self.tr("Save Project"), self) + self.aSaveProject.setStatusTip(self.tr("Save project")) self.aSaveProject.setShortcut("Ctrl+Shift+S") self.aSaveProject.triggered.connect(lambda: self.theParent.saveProject()) self.projMenu.addAction(self.aSaveProject) # Project > Close Project - self.aCloseProject = QAction("Close Project", self) - self.aCloseProject.setStatusTip("Close project") + self.aCloseProject = QAction(self.tr("Close Project"), self) + self.aCloseProject.setStatusTip(self.tr("Close project")) self.aCloseProject.setShortcut("Ctrl+Shift+W") self.aCloseProject.triggered.connect(lambda: self.theParent.closeProject(False)) self.projMenu.addAction(self.aCloseProject) @@ -213,15 +213,15 @@ class GuiMainMenu(QMenuBar): self.projMenu.addSeparator() # Project > Project Settings - self.aProjectSettings = QAction("Project Settings", self) - self.aProjectSettings.setStatusTip("Project settings") + self.aProjectSettings = QAction(self.tr("Project Settings"), self) + self.aProjectSettings.setStatusTip(self.tr("Project settings")) self.aProjectSettings.setShortcut("Ctrl+Shift+,") self.aProjectSettings.triggered.connect(lambda: self.theParent.showProjectSettingsDialog()) self.projMenu.addAction(self.aProjectSettings) # Project > Project Details - self.aProjectDetails = QAction("Project Details", self) - self.aProjectDetails.setStatusTip("Project details") + self.aProjectDetails = QAction(self.tr("Project Details"), self) + self.aProjectDetails.setStatusTip(self.tr("Project details")) self.aProjectDetails.setShortcut("Shift+F6") self.aProjectDetails.triggered.connect(lambda: self.theParent.showProjectDetailsDialog()) self.projMenu.addAction(self.aProjectDetails) @@ -230,17 +230,17 @@ class GuiMainMenu(QMenuBar): self.projMenu.addSeparator() # Project > New Root - self.rootMenu = self.projMenu.addMenu("Create Root Folder") + self.rootMenu = self.projMenu.addMenu(self.tr("Create Root Folder")) self.rootItems = {} - self.rootItems[nwItemClass.NOVEL] = QAction("Novel Root", self.rootMenu) - self.rootItems[nwItemClass.PLOT] = QAction("Plot Root", self.rootMenu) - self.rootItems[nwItemClass.CHARACTER] = QAction("Character Root", self.rootMenu) - self.rootItems[nwItemClass.WORLD] = QAction("Location Root", self.rootMenu) - self.rootItems[nwItemClass.TIMELINE] = QAction("Timeline Root", self.rootMenu) - self.rootItems[nwItemClass.OBJECT] = QAction("Object Root", self.rootMenu) - self.rootItems[nwItemClass.ENTITY] = QAction("Entity Root", self.rootMenu) - self.rootItems[nwItemClass.CUSTOM] = QAction("Custom Root", self.rootMenu) - self.rootItems[nwItemClass.ARCHIVE] = QAction("Outtakes Root", self.rootMenu) + self.rootItems[nwItemClass.NOVEL] = QAction(self.tr("Novel Root"), self.rootMenu) + self.rootItems[nwItemClass.PLOT] = QAction(self.tr("Plot Root"), self.rootMenu) + self.rootItems[nwItemClass.CHARACTER] = QAction(self.tr("Character Root"), self.rootMenu) + self.rootItems[nwItemClass.WORLD] = QAction(self.tr("Location Root"), self.rootMenu) + self.rootItems[nwItemClass.TIMELINE] = QAction(self.tr("Timeline Root"), self.rootMenu) + self.rootItems[nwItemClass.OBJECT] = QAction(self.tr("Object Root"), self.rootMenu) + self.rootItems[nwItemClass.ENTITY] = QAction(self.tr("Entity Root"), self.rootMenu) + self.rootItems[nwItemClass.CUSTOM] = QAction(self.tr("Custom Root"), self.rootMenu) + self.rootItems[nwItemClass.ARCHIVE] = QAction(self.tr("Outtakes Root"), self.rootMenu) nCount = 0 for itemClass in self.rootItems.keys(): nCount += 1 # This forces the lambdas to be unique @@ -250,8 +250,8 @@ class GuiMainMenu(QMenuBar): self.rootMenu.addAction(self.rootItems[itemClass]) # Project > New Folder - self.aCreateFolder = QAction("Create Folder", self) - self.aCreateFolder.setStatusTip("Create folder") + self.aCreateFolder = QAction(self.tr("Create Folder"), self) + self.aCreateFolder.setStatusTip(self.tr("Create folder")) self.aCreateFolder.setShortcut("Ctrl+Shift+N") self.aCreateFolder.triggered.connect(lambda: self._newTreeItem(nwItemType.FOLDER, None)) self.projMenu.addAction(self.aCreateFolder) @@ -260,43 +260,43 @@ class GuiMainMenu(QMenuBar): self.projMenu.addSeparator() # Project > Edit - self.aEditItem = QAction("Edit Item", self) - self.aEditItem.setStatusTip("Change project item settings") + self.aEditItem = QAction(self.tr("Edit Item"), self) + self.aEditItem.setStatusTip(self.tr("Change project item settings")) self.aEditItem.setShortcuts(["Ctrl+E", "F2"]) self.aEditItem.triggered.connect(lambda: self.theParent.editItem(None)) self.projMenu.addAction(self.aEditItem) # Project > Delete - self.aDeleteItem = QAction("Delete Item", self) - self.aDeleteItem.setStatusTip("Delete selected project item") + self.aDeleteItem = QAction(self.tr("Delete Item"), self) + self.aDeleteItem.setStatusTip(self.tr("Delete selected project item")) self.aDeleteItem.setShortcut("Ctrl+Shift+Del") self.aDeleteItem.triggered.connect(lambda: self.theParent.treeView.deleteItem(None)) self.projMenu.addAction(self.aDeleteItem) # Project > Move Up - self.aMoveUp = QAction("Move Item Up", self) - self.aMoveUp.setStatusTip("Move project item up") + self.aMoveUp = QAction(self.tr("Move Item Up"), self) + self.aMoveUp.setStatusTip(self.tr("Move project item up")) self.aMoveUp.setShortcut("Ctrl+Up") self.aMoveUp.triggered.connect(lambda: self._moveTreeItem(-1)) self.projMenu.addAction(self.aMoveUp) # Project > Move Down - self.aMoveDown = QAction("Move Item Down", self) - self.aMoveDown.setStatusTip("Move project item down") + self.aMoveDown = QAction(self.tr("Move Item Down"), self) + self.aMoveDown.setStatusTip(self.tr("Move project item down")) self.aMoveDown.setShortcut("Ctrl+Down") self.aMoveDown.triggered.connect(lambda: self._moveTreeItem(1)) self.projMenu.addAction(self.aMoveDown) # Project > Undo Last Action - self.aMoveUndo = QAction("Undo Last Move", self) - self.aMoveUndo.setStatusTip("Undo last item move") + self.aMoveUndo = QAction(self.tr("Undo Last Move"), self) + self.aMoveUndo.setStatusTip(self.tr("Undo last item move")) self.aMoveUndo.setShortcut("Ctrl+Shift+Z") self.aMoveUndo.triggered.connect(lambda: self.theParent.treeView.undoLastMove()) self.projMenu.addAction(self.aMoveUndo) # Project > Empty Trash - self.aEmptyTrash = QAction("Empty Trash", self) - self.aEmptyTrash.setStatusTip("Permanently delete all files in the Trash folder") + self.aEmptyTrash = QAction(self.tr("Empty Trash"), self) + self.aEmptyTrash.setStatusTip(self.tr("Permanently delete all files in the Trash folder")) self.aEmptyTrash.triggered.connect(lambda: self.theParent.treeView.emptyTrash()) self.projMenu.addAction(self.aEmptyTrash) @@ -304,8 +304,8 @@ class GuiMainMenu(QMenuBar): self.projMenu.addSeparator() # Project > Exit - self.aExitNW = QAction("Exit", self) - self.aExitNW.setStatusTip("Exit novelWriter") + self.aExitNW = QAction(self.tr("Exit"), self) + self.aExitNW.setStatusTip(self.tr("Exit novelWriter")) self.aExitNW.setShortcut("Ctrl+Q") self.aExitNW.setMenuRole(QAction.QuitRole) self.aExitNW.triggered.connect(lambda: self.theParent.closeMain()) @@ -317,32 +317,32 @@ class GuiMainMenu(QMenuBar): """Assemble the Document menu. """ # Document - self.docuMenu = self.addMenu("&Document") + self.docuMenu = self.addMenu(self.tr("&Document")) # Document > New - self.aNewDoc = QAction("New Document", self) - self.aNewDoc.setStatusTip("Create new document") + self.aNewDoc = QAction(self.tr("New Document"), self) + self.aNewDoc.setStatusTip(self.tr("Create new document")) self.aNewDoc.setShortcut("Ctrl+N") self.aNewDoc.triggered.connect(lambda: self._newTreeItem(nwItemType.FILE, None)) self.docuMenu.addAction(self.aNewDoc) # Document > Open - self.aOpenDoc = QAction("Open Document", self) - self.aOpenDoc.setStatusTip("Open selected document") + self.aOpenDoc = QAction(self.tr("Open Document"), self) + self.aOpenDoc.setStatusTip(self.tr("Open selected document")) self.aOpenDoc.setShortcut("Ctrl+O") self.aOpenDoc.triggered.connect(lambda: self.theParent.openSelectedItem()) self.docuMenu.addAction(self.aOpenDoc) # Document > Save - self.aSaveDoc = QAction("Save Document", self) - self.aSaveDoc.setStatusTip("Save current document") + self.aSaveDoc = QAction(self.tr("Save Document"), self) + self.aSaveDoc.setStatusTip(self.tr("Save current document")) self.aSaveDoc.setShortcut("Ctrl+S") self.aSaveDoc.triggered.connect(lambda: self.theParent.saveDocument()) self.docuMenu.addAction(self.aSaveDoc) # Document > Close - self.aCloseDoc = QAction("Close Document", self) - self.aCloseDoc.setStatusTip("Close current document") + self.aCloseDoc = QAction(self.tr("Close Document"), self) + self.aCloseDoc.setStatusTip(self.tr("Close current document")) self.aCloseDoc.setShortcut("Ctrl+W") self.aCloseDoc.triggered.connect(lambda: self.theParent.closeDocEditor()) self.docuMenu.addAction(self.aCloseDoc) @@ -351,15 +351,15 @@ class GuiMainMenu(QMenuBar): self.docuMenu.addSeparator() # Document > Preview - self.aViewDoc = QAction("View Document", self) - self.aViewDoc.setStatusTip("View document as HTML") + self.aViewDoc = QAction(self.tr("View Document"), self) + self.aViewDoc.setStatusTip(self.tr("View document as HTML")) self.aViewDoc.setShortcut("Ctrl+R") self.aViewDoc.triggered.connect(lambda: self.theParent.viewDocument(None)) self.docuMenu.addAction(self.aViewDoc) # Document > Close Preview - self.aCloseView = QAction("Close Document View", self) - self.aCloseView.setStatusTip("Close document view pane") + self.aCloseView = QAction(self.tr("Close Document View"), self) + self.aCloseView.setStatusTip(self.tr("Close document view pane")) self.aCloseView.setShortcut("Ctrl+Shift+R") self.aCloseView.triggered.connect(lambda: self.theParent.closeDocViewer()) self.docuMenu.addAction(self.aCloseView) @@ -368,29 +368,30 @@ class GuiMainMenu(QMenuBar): self.docuMenu.addSeparator() # Document > Show File Details - self.aFileDetails = QAction("Show File Details", self) + self.aFileDetails = QAction(self.tr("Show File Details"), self) self.aFileDetails.setStatusTip( - "Shows a message box with the document location in the project folder" + self.tr("Shows a message box with the document location in the project folder") ) self.aFileDetails.triggered.connect(lambda: self.theParent.docEditor.revealLocation()) self.docuMenu.addAction(self.aFileDetails) # Document > Import From File - self.aImportFile = QAction("Import from File", self) - self.aImportFile.setStatusTip("Import document from a text or markdown file") + self.aImportFile = QAction(self.tr("Import from File"), self) + self.aImportFile.setStatusTip(self.tr("Import document from a text or markdown file")) self.aImportFile.setShortcut("Ctrl+Shift+I") self.aImportFile.triggered.connect(lambda: self.theParent.importDocument()) self.docuMenu.addAction(self.aImportFile) # Document > Merge Documents - self.aMergeDocs = QAction("Merge Folder to Document", self) - self.aMergeDocs.setStatusTip("Merge a folder of documents to a single document") + self.aMergeDocs = QAction(self.tr("Merge Folder to Document"), self) + self.aMergeDocs.setStatusTip(self.tr("Merge a folder of documents to a single document")) self.aMergeDocs.triggered.connect(lambda: self.theParent.mergeDocuments()) self.docuMenu.addAction(self.aMergeDocs) # Document > Split Document - self.aSplitDoc = QAction("Split Document to Folder", self) - self.aSplitDoc.setStatusTip("Split a document into a folder of multiple documents") + self.aSplitDoc = QAction(self.tr("Split Document to Folder"), self) + self.aSplitDoc.setStatusTip(self.tr("Split a document into a folder of " + "multiple documents")) self.aSplitDoc.triggered.connect(lambda: self.theParent.splitDocument()) self.docuMenu.addAction(self.aSplitDoc) @@ -400,18 +401,18 @@ class GuiMainMenu(QMenuBar): """Assemble the Edit menu. """ # Edit - self.editMenu = self.addMenu("&Edit") + self.editMenu = self.addMenu(self.tr("&Edit")) # Edit > Undo - self.aEditUndo = QAction("Undo", self) - self.aEditUndo.setStatusTip("Undo last change") + self.aEditUndo = QAction(self.tr("Undo"), self) + self.aEditUndo.setStatusTip(self.tr("Undo last change")) self.aEditUndo.setShortcut("Ctrl+Z") self.aEditUndo.triggered.connect(lambda: self._docAction(nwDocAction.UNDO)) self.editMenu.addAction(self.aEditUndo) # Edit > Redo - self.aEditRedo = QAction("Redo", self) - self.aEditRedo.setStatusTip("Redo last change") + self.aEditRedo = QAction(self.tr("Redo"), self) + self.aEditRedo.setStatusTip(self.tr("Redo last change")) self.aEditRedo.setShortcut("Ctrl+Y") self.aEditRedo.triggered.connect(lambda: self._docAction(nwDocAction.REDO)) self.editMenu.addAction(self.aEditRedo) @@ -420,22 +421,22 @@ class GuiMainMenu(QMenuBar): self.editMenu.addSeparator() # Edit > Cut - self.aEditCut = QAction("Cut", self) - self.aEditCut.setStatusTip("Cut selected text") + self.aEditCut = QAction(self.tr("Cut"), self) + self.aEditCut.setStatusTip(self.tr("Cut selected text")) self.aEditCut.setShortcut("Ctrl+X") self.aEditCut.triggered.connect(lambda: self._docAction(nwDocAction.CUT)) self.editMenu.addAction(self.aEditCut) # Edit > Copy - self.aEditCopy = QAction("Copy", self) - self.aEditCopy.setStatusTip("Copy selected text") + self.aEditCopy = QAction(self.tr("Copy"), self) + self.aEditCopy.setStatusTip(self.tr("Copy selected text")) self.aEditCopy.setShortcut("Ctrl+C") self.aEditCopy.triggered.connect(lambda: self._docAction(nwDocAction.COPY)) self.editMenu.addAction(self.aEditCopy) # Edit > Paste - self.aEditPaste = QAction("Paste", self) - self.aEditPaste.setStatusTip("Paste text from clipboard") + self.aEditPaste = QAction(self.tr("Paste"), self) + self.aEditPaste.setStatusTip(self.tr("Paste text from clipboard")) self.aEditPaste.setShortcut("Ctrl+V") self.aEditPaste.triggered.connect(lambda: self._docAction(nwDocAction.PASTE)) self.editMenu.addAction(self.aEditPaste) @@ -444,15 +445,15 @@ class GuiMainMenu(QMenuBar): self.editMenu.addSeparator() # Edit > Select All - self.aSelectAll = QAction("Select All", self) - self.aSelectAll.setStatusTip("Select all text in document") + self.aSelectAll = QAction(self.tr("Select All"), self) + self.aSelectAll.setStatusTip(self.tr("Select all text in document")) self.aSelectAll.setShortcut("Ctrl+A") self.aSelectAll.triggered.connect(lambda: self._docAction(nwDocAction.SEL_ALL)) self.editMenu.addAction(self.aSelectAll) # Edit > Select Paragraph - self.aSelectPar = QAction("Select Paragraph", self) - self.aSelectPar.setStatusTip("Select all text in paragraph") + self.aSelectPar = QAction(self.tr("Select Paragraph"), self) + self.aSelectPar.setStatusTip(self.tr("Select all text in paragraph")) self.aSelectPar.setShortcut("Ctrl+Shift+A") self.aSelectPar.triggered.connect(lambda: self._docAction(nwDocAction.SEL_PARA)) self.editMenu.addAction(self.aSelectPar) @@ -463,32 +464,32 @@ class GuiMainMenu(QMenuBar): """Assemble the View menu. """ # View - self.viewMenu = self.addMenu("&View") + self.viewMenu = self.addMenu(self.tr("&View")) # View > TreeView - self.aFocusTree = QAction("Focus Project Tree", self) - self.aFocusTree.setStatusTip("Move focus to project tree") + self.aFocusTree = QAction(self.tr("Focus Project Tree"), self) + self.aFocusTree.setStatusTip(self.tr("Move focus to project tree")) self.aFocusTree.setShortcut("Alt+1") self.aFocusTree.triggered.connect(lambda: self.theParent.setFocus(1)) self.viewMenu.addAction(self.aFocusTree) # View > Document Pane 1 - self.aFocusEditor = QAction("Focus Document Editor", self) - self.aFocusEditor.setStatusTip("Move focus to left document pane") + self.aFocusEditor = QAction(self.tr("Focus Document Editor"), self) + self.aFocusEditor.setStatusTip(self.tr("Move focus to left document pane")) self.aFocusEditor.setShortcut("Alt+2") self.aFocusEditor.triggered.connect(lambda: self.theParent.setFocus(2)) self.viewMenu.addAction(self.aFocusEditor) # View > Document Pane 2 - self.aFocusView = QAction("Focus Document Viewer", self) - self.aFocusView.setStatusTip("Move focus to right document pane") + self.aFocusView = QAction(self.tr("Focus Document Viewer"), self) + self.aFocusView.setStatusTip(self.tr("Move focus to right document pane")) self.aFocusView.setShortcut("Alt+3") self.aFocusView.triggered.connect(lambda: self.theParent.setFocus(3)) self.viewMenu.addAction(self.aFocusView) # View > Outline - self.aFocusOutline = QAction("Focus Outline", self) - self.aFocusOutline.setStatusTip("Move focus to outline") + self.aFocusOutline = QAction(self.tr("Focus Outline"), self) + self.aFocusOutline.setStatusTip(self.tr("Move focus to outline")) self.aFocusOutline.setShortcut("Alt+4") self.aFocusOutline.triggered.connect(lambda: self.theParent.setFocus(4)) self.viewMenu.addAction(self.aFocusOutline) @@ -497,15 +498,15 @@ class GuiMainMenu(QMenuBar): self.viewMenu.addSeparator() # View > Go Backward - self.aViewPrev = QAction("Go Backward", self) - self.aViewPrev.setStatusTip("Move backward in the view history of the right pane") + self.aViewPrev = QAction(self.tr("Go Backward"), self) + self.aViewPrev.setStatusTip(self.tr("Move backward in the view history of the right pane")) self.aViewPrev.setShortcut("Alt+Left") self.aViewPrev.triggered.connect(lambda: self.theParent.docViewer.navBackward()) self.viewMenu.addAction(self.aViewPrev) # View > Go Forward - self.aViewNext = QAction("Go Forward", self) - self.aViewNext.setStatusTip("Move forward in the view history of the right pane") + self.aViewNext = QAction(self.tr("Go Forward"), self) + self.aViewNext.setStatusTip(self.tr("Move forward in the view history of the right pane")) self.aViewNext.setShortcut("Alt+Right") self.aViewNext.triggered.connect(lambda: self.theParent.docViewer.navForward()) self.viewMenu.addAction(self.aViewNext) @@ -514,8 +515,9 @@ class GuiMainMenu(QMenuBar): self.viewMenu.addSeparator() # View > Focus Mode - self.aFocusMode = QAction("Focus Mode", self) - self.aFocusMode.setStatusTip("Toggles a distraction free mode, only showing text editor") + self.aFocusMode = QAction(self.tr("Focus Mode"), self) + self.aFocusMode.setStatusTip(self.tr("Toggles a distraction free mode, " + "only showing text editor")) self.aFocusMode.setShortcut("F8") self.aFocusMode.setCheckable(True) self.aFocusMode.setChecked(self.theParent.isFocusMode) @@ -523,8 +525,8 @@ class GuiMainMenu(QMenuBar): self.viewMenu.addAction(self.aFocusMode) # View > Toggle Full Screen - self.aFullScreen = QAction("Full Screen Mode", self) - self.aFullScreen.setStatusTip("Maximises the main window") + self.aFullScreen = QAction(self.tr("Full Screen Mode"), self) + self.aFullScreen.setStatusTip(self.tr("Maximises the main window")) self.aFullScreen.setShortcut("F11") self.aFullScreen.triggered.connect(lambda: self.theParent.toggleFullScreenMode()) self.viewMenu.addAction(self.aFullScreen) @@ -535,187 +537,188 @@ class GuiMainMenu(QMenuBar): """Assemble the Insert menu. """ # Insert - self.insertMenu = self.addMenu("&Insert") + self.insertMenu = self.addMenu(self.tr("&Insert")) - # Insert > Dashes - self.mInsDashes = self.insertMenu.addMenu("Dashes") + # Insert > Dashes and Dots + self.mInsDashes = self.insertMenu.addMenu(self.tr("Dashes")) # Insert > Short Dash - self.aInsENDash = QAction("Short Dash", self) - self.aInsENDash.setStatusTip("Insert short dash (en dash)") + self.aInsENDash = QAction(self.tr("Short Dash"), self) + self.aInsENDash.setStatusTip(self.tr("Insert short dash (en dash)")) self.aInsENDash.setShortcut("Ctrl+K, -") self.aInsENDash.triggered.connect(lambda: self._docInsert(nwUnicode.U_ENDASH)) self.mInsDashes.addAction(self.aInsENDash) # Insert > Long Dash - self.aInsEMDash = QAction("Long Dash", self) - self.aInsEMDash.setStatusTip("Insert long dash (em dash)") + self.aInsEMDash = QAction(self.tr("Long Dash"), self) + self.aInsEMDash.setStatusTip(self.tr("Insert long dash (em dash)")) self.aInsEMDash.setShortcut("Ctrl+K, _") self.aInsEMDash.triggered.connect(lambda: self._docInsert(nwUnicode.U_EMDASH)) self.mInsDashes.addAction(self.aInsEMDash) # Insert > Long Dash - self.aInsHorBar = QAction("Horizontal Bar", self) - self.aInsHorBar.setStatusTip("Insert a horizontal bar (quotation dash)") + self.aInsHorBar = QAction(self.tr("Horizontal Bar"), self) + self.aInsHorBar.setStatusTip(self.tr("Insert a horizontal bar (quotation dash)")) self.aInsHorBar.setShortcut("Ctrl+K, Ctrl+_") self.aInsHorBar.triggered.connect(lambda: self._docInsert(nwUnicode.U_HBAR)) self.mInsDashes.addAction(self.aInsHorBar) # Insert > Figure Dash - self.aInsFigDash = QAction("Figure Dash", self) - self.aInsFigDash.setStatusTip("Insert figure dash (same width as a number character)") + self.aInsFigDash = QAction(self.tr("Figure Dash"), self) + self.aInsFigDash.setStatusTip( + self.tr("Insert figure dash (same width as a number character)")) self.aInsFigDash.setShortcut("Ctrl+K, ~") self.aInsFigDash.triggered.connect(lambda: self._docInsert(nwUnicode.U_FGDASH)) self.mInsDashes.addAction(self.aInsFigDash) # Insert > Quote Marks - self.mInsQuotes = self.insertMenu.addMenu("Quote Marks") + self.mInsQuotes = self.insertMenu.addMenu(self.tr("Quote Marks")) # Insert > Left Single Quote - self.aInsQuoteLS = QAction("Left Single Quote", self) - self.aInsQuoteLS.setStatusTip("Insert left single quote") + self.aInsQuoteLS = QAction(self.tr("Left Single Quote"), self) + self.aInsQuoteLS.setStatusTip(self.tr("Insert left single quote")) self.aInsQuoteLS.setShortcut("Ctrl+K, 1") self.aInsQuoteLS.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_LS)) self.mInsQuotes.addAction(self.aInsQuoteLS) # Insert > Right Single Quote - self.aInsQuoteRS = QAction("Right Single Quote", self) - self.aInsQuoteRS.setStatusTip("Insert right single quote") + self.aInsQuoteRS = QAction(self.tr("Right Single Quote"), self) + self.aInsQuoteRS.setStatusTip(self.tr("Insert right single quote")) self.aInsQuoteRS.setShortcut("Ctrl+K, 2") self.aInsQuoteRS.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_RS)) self.mInsQuotes.addAction(self.aInsQuoteRS) # Insert > Left Double Quote - self.aInsQuoteLD = QAction("Left Double Quote", self) - self.aInsQuoteLD.setStatusTip("Insert left double quote") + self.aInsQuoteLD = QAction(self.tr("Left Double Quote"), self) + self.aInsQuoteLD.setStatusTip(self.tr("Insert left double quote")) self.aInsQuoteLD.setShortcut("Ctrl+K, 3") self.aInsQuoteLD.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_LD)) self.mInsQuotes.addAction(self.aInsQuoteLD) # Insert > Right Double Quote - self.aInsQuoteRD = QAction("Right Double Quote", self) - self.aInsQuoteRD.setStatusTip("Insert right double quote") + self.aInsQuoteRD = QAction(self.tr("Right Double Quote"), self) + self.aInsQuoteRD.setStatusTip(self.tr("Insert right double quote")) self.aInsQuoteRD.setShortcut("Ctrl+K, 4") self.aInsQuoteRD.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_RD)) self.mInsQuotes.addAction(self.aInsQuoteRD) # Insert > Alternative Apostrophe - self.aInsMSApos = QAction("Alternative Apostrophe", self) - self.aInsMSApos.setStatusTip("Insert modifier letter single apostrophe") + self.aInsMSApos = QAction(self.tr("Alternative Apostrophe"), self) + self.aInsMSApos.setStatusTip(self.tr("Insert modifier letter single apostrophe")) self.aInsMSApos.setShortcut("Ctrl+K, '") self.aInsMSApos.triggered.connect(lambda: self._docInsert(nwUnicode.U_MAPOSS)) self.mInsQuotes.addAction(self.aInsMSApos) # Insert > Symbols - self.mInsPunct = self.insertMenu.addMenu("General Punctuation") + self.mInsPunct = self.insertMenu.addMenu(self.tr("General Punctuation")) # Insert > Ellipsis - self.aInsEllipsis = QAction("Ellipsis", self) - self.aInsEllipsis.setStatusTip("Insert ellipsis") + self.aInsEllipsis = QAction(self.tr("Ellipsis"), self) + self.aInsEllipsis.setStatusTip(self.tr("Insert ellipsis")) self.aInsEllipsis.setShortcut("Ctrl+K, .") self.aInsEllipsis.triggered.connect(lambda: self._docInsert(nwUnicode.U_HELLIP)) self.mInsPunct.addAction(self.aInsEllipsis) # Insert > Prime - self.aInsPrime = QAction("Prime", self) - self.aInsPrime.setStatusTip("Insert a prime symbol") + self.aInsPrime = QAction(self.tr("Prime"), self) + self.aInsPrime.setStatusTip(self.tr("Insert a prime symbol")) self.aInsPrime.setShortcut("Ctrl+K, Ctrl+'") self.aInsPrime.triggered.connect(lambda: self._docInsert(nwUnicode.U_PRIME)) self.mInsPunct.addAction(self.aInsPrime) # Insert > Double Prime - self.aInsDPrime = QAction("Double Prime", self) - self.aInsDPrime.setStatusTip("Insert a double prime symbol") + self.aInsDPrime = QAction(self.tr("Double Prime"), self) + self.aInsDPrime.setStatusTip(self.tr("Insert a double prime symbol")) self.aInsDPrime.setShortcut("Ctrl+K, Ctrl+\"") self.aInsDPrime.triggered.connect(lambda: self._docInsert(nwUnicode.U_DPRIME)) self.mInsPunct.addAction(self.aInsDPrime) # Insert > Breaks and Spaces - self.mInsBreaks = self.insertMenu.addMenu("Breaks and Spaces") + self.mInsBreaks = self.insertMenu.addMenu(self.tr("Breaks and Spaces")) # Insert > Hard Line Break - self.aInsHardBreak = QAction("Hard Line Break", self) - self.aInsHardBreak.setStatusTip("Insert a hard line break") + self.aInsHardBreak = QAction(self.tr("Hard Line Break"), self) + self.aInsHardBreak.setStatusTip(self.tr("Insert a hard line break")) self.aInsHardBreak.setShortcut("Ctrl+K, Return") self.aInsHardBreak.triggered.connect(lambda: self._docInsert(nwDocInsert.HARD_BREAK)) self.mInsBreaks.addAction(self.aInsHardBreak) # Insert > Non-Breaking Space - self.aInsNBSpace = QAction("Non-Breaking Space", self) - self.aInsNBSpace.setStatusTip("Insert a non-breaking space") + self.aInsNBSpace = QAction(self.tr("Non-Breaking Space"), self) + self.aInsNBSpace.setStatusTip(self.tr("Insert a non-breaking space")) self.aInsNBSpace.setShortcut("Ctrl+K, Space") self.aInsNBSpace.triggered.connect(lambda: self._docInsert(nwUnicode.U_NBSP)) self.mInsBreaks.addAction(self.aInsNBSpace) # Insert > Thin Space - self.aInsThinSpace = QAction("Thin Space", self) - self.aInsThinSpace.setStatusTip("Insert a thin space") + self.aInsThinSpace = QAction(self.tr("Thin Space"), self) + self.aInsThinSpace.setStatusTip(self.tr("Insert a thin space")) self.aInsThinSpace.setShortcut("Ctrl+K, Shift+Space") self.aInsThinSpace.triggered.connect(lambda: self._docInsert(nwUnicode.U_THSP)) self.mInsBreaks.addAction(self.aInsThinSpace) # Insert > Thin Non-Breaking Space - self.aInsThinNBSpace = QAction("Thin Non-Breaking Space", self) - self.aInsThinNBSpace.setStatusTip("Insert a thin non-breaking space") + self.aInsThinNBSpace = QAction(self.tr("Thin Non-Breaking Space"), self) + self.aInsThinNBSpace.setStatusTip(self.tr("Insert a thin non-breaking space")) self.aInsThinNBSpace.setShortcut("Ctrl+K, Ctrl+Space") self.aInsThinNBSpace.triggered.connect(lambda: self._docInsert(nwUnicode.U_THNBSP)) self.mInsBreaks.addAction(self.aInsThinNBSpace) # Insert > Symbols - self.mInsSymbol = self.insertMenu.addMenu("Other Symbols") + self.mInsSymbol = self.insertMenu.addMenu(self.tr("Other Symbols")) # Insert > List Bullet - self.aInsBullet = QAction("List Bullet", self) - self.aInsBullet.setStatusTip("Insert a list bullet") + self.aInsBullet = QAction(self.tr("List Bullet"), self) + self.aInsBullet.setStatusTip(self.tr("Insert a list bullet")) self.aInsBullet.setShortcut("Ctrl+K, *") self.aInsBullet.triggered.connect(lambda: self._docInsert(nwUnicode.U_BULL)) self.mInsSymbol.addAction(self.aInsBullet) # Insert > Hyphen Bullet - self.aInsHyBull = QAction("Hyphen Bullet", self) - self.aInsHyBull.setStatusTip("Insert a hyphen bullet (alternative bullet)") + self.aInsHyBull = QAction(self.tr("Hyphen Bullet"), self) + self.aInsHyBull.setStatusTip(self.tr("Insert a hyphen bullet (alternative bullet)")) self.aInsHyBull.setShortcut("Ctrl+K, Ctrl+-") self.aInsHyBull.triggered.connect(lambda: self._docInsert(nwUnicode.U_HYBULL)) self.mInsSymbol.addAction(self.aInsHyBull) # Insert > Flower Mark - self.aInsFlower = QAction("Flower Mark", self) - self.aInsFlower.setStatusTip("Insert a flower mark (alternative bullet)") + self.aInsFlower = QAction(self.tr("Flower Mark"), self) + self.aInsFlower.setStatusTip(self.tr("Insert a flower mark (alternative bullet)")) self.aInsFlower.setShortcut("Ctrl+K, Ctrl+*") self.aInsFlower.triggered.connect(lambda: self._docInsert(nwUnicode.U_FLOWER)) self.mInsSymbol.addAction(self.aInsFlower) # Insert > Per Mille - self.aInsPerMille = QAction("Per Mille", self) - self.aInsPerMille.setStatusTip("Insert a per mille symbol") + self.aInsPerMille = QAction(self.tr("Per Mille"), self) + self.aInsPerMille.setStatusTip(self.tr("Insert a per mille symbol")) self.aInsPerMille.setShortcut("Ctrl+K, %") self.aInsPerMille.triggered.connect(lambda: self._docInsert(nwUnicode.U_PERMIL)) self.mInsSymbol.addAction(self.aInsPerMille) # Insert > Degree Symbol - self.aInsDegree = QAction("Degree Symbol", self) - self.aInsDegree.setStatusTip("Insert a degree symbol") + self.aInsDegree = QAction(self.tr("Degree Symbol"), self) + self.aInsDegree.setStatusTip(self.tr("Insert a degree symbol")) self.aInsDegree.setShortcut("Ctrl+K, Ctrl+O") self.aInsDegree.triggered.connect(lambda: self._docInsert(nwUnicode.U_DEGREE)) self.mInsSymbol.addAction(self.aInsDegree) # Insert > Minus Sign - self.aInsMinus = QAction("Minus Sign", self) - self.aInsMinus.setStatusTip("Insert a minus sign (not a hypen or dash)") + self.aInsMinus = QAction(self.tr("Minus Sign"), self) + self.aInsMinus.setStatusTip(self.tr("Insert a minus sign (not a hypen or dash)")) self.aInsMinus.setShortcut("Ctrl+K, Ctrl+M") self.aInsMinus.triggered.connect(lambda: self._docInsert(nwUnicode.U_MINUS)) self.mInsSymbol.addAction(self.aInsMinus) # Insert > Times Sign - self.aInsTimes = QAction("Times Sign", self) - self.aInsTimes.setStatusTip("Insert a times sign (multiplication cross)") + self.aInsTimes = QAction(self.tr("Times Sign"), self) + self.aInsTimes.setStatusTip(self.tr("Insert a times sign (multiplication cross)")) self.aInsTimes.setShortcut("Ctrl+K, Ctrl+X") self.aInsTimes.triggered.connect(lambda: self._docInsert(nwUnicode.U_TIMES)) self.mInsSymbol.addAction(self.aInsTimes) # Insert > Division - self.aInsDivide = QAction("Division Sign", self) - self.aInsDivide.setStatusTip("Insert a division sign") + self.aInsDivide = QAction(self.tr("Division Sign"), self) + self.aInsDivide.setStatusTip(self.tr("Insert a division sign")) self.aInsDivide.setShortcut("Ctrl+K, Ctrl+D") self.aInsDivide.triggered.connect(lambda: self._docInsert(nwUnicode.U_DIVIDE)) self.mInsSymbol.addAction(self.aInsDivide) @@ -724,7 +727,7 @@ class GuiMainMenu(QMenuBar): self.insertMenu.addSeparator() # Insert > Tags and References - self.mInsKeywords = self.insertMenu.addMenu("Tags and References") + self.mInsKeywords = self.insertMenu.addMenu(self.tr("Tags and References")) self.mInsKWItems = {} self.mInsKWItems[nwKeyWords.TAG_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, G") self.mInsKWItems[nwKeyWords.POV_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, V") @@ -737,7 +740,8 @@ class GuiMainMenu(QMenuBar): self.mInsKWItems[nwKeyWords.ENTITY_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, E") self.mInsKWItems[nwKeyWords.CUSTOM_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, X") for n, keyWord in enumerate(self.mInsKWItems): - self.mInsKWItems[keyWord][0].setText(nwLabels.KEY_NAME[keyWord]) + self.mInsKWItems[keyWord][0].setText( + QCoreApplication.translate("Constant", nwLabels.KEY_NAME[keyWord])) self.mInsKWItems[keyWord][0].setShortcut(self.mInsKWItems[keyWord][1]) self.mInsKWItems[keyWord][0].triggered.connect( lambda n, keyWord=keyWord: self._insertKeyWord(keyWord) @@ -750,18 +754,18 @@ class GuiMainMenu(QMenuBar): """Assemble the Search menu. """ # Search - self.srcMenu = self.addMenu("&Search") + self.srcMenu = self.addMenu(self.tr("&Search")) # Search > Find - self.aFind = QAction("Find", self) - self.aFind.setStatusTip("Find text in document") + self.aFind = QAction(self.tr("Find"), self) + self.aFind.setStatusTip(self.tr("Find text in document")) self.aFind.setShortcut("Ctrl+F") self.aFind.triggered.connect(lambda: self._docAction(nwDocAction.FIND)) self.srcMenu.addAction(self.aFind) # Search > Replace - self.aReplace = QAction("Replace", self) - self.aReplace.setStatusTip("Replace text in document") + self.aReplace = QAction(self.tr("Replace"), self) + self.aReplace.setStatusTip(self.tr("Replace text in document")) if self.mainConf.osDarwin: self.aReplace.setShortcut("Ctrl+=") else: @@ -770,8 +774,8 @@ class GuiMainMenu(QMenuBar): self.srcMenu.addAction(self.aReplace) # Search > Find Next - self.aFindNext = QAction("Find Next", self) - self.aFindNext.setStatusTip("Find next occurrence text in document") + self.aFindNext = QAction(self.tr("Find Next"), self) + self.aFindNext.setStatusTip(self.tr("Find next occurrence text in document")) if self.mainConf.osDarwin: self.aFindNext.setShortcuts(["Ctrl+G", "F3"]) else: @@ -780,8 +784,8 @@ class GuiMainMenu(QMenuBar): self.srcMenu.addAction(self.aFindNext) # Search > Find Prev - self.aFindPrev = QAction("Find Previous", self) - self.aFindPrev.setStatusTip("Find previous occurrence text in document") + self.aFindPrev = QAction(self.tr("Find Previous"), self) + self.aFindPrev.setStatusTip(self.tr("Find previous occurrence text in document")) if self.mainConf.osDarwin: self.aFindPrev.setShortcuts(["Ctrl+Shift+G", "Shift+F3"]) else: @@ -790,8 +794,9 @@ class GuiMainMenu(QMenuBar): self.srcMenu.addAction(self.aFindPrev) # Search > Replace Next - self.aReplaceNext = QAction("Replace Next", self) - self.aReplaceNext.setStatusTip("Find and replace next occurrence text in document") + self.aReplaceNext = QAction(self.tr("Replace Next"), self) + self.aReplaceNext.setStatusTip( + self.tr("Find and replace next occurrence text in document")) self.aReplaceNext.setShortcut("Ctrl+Shift+1") self.aReplaceNext.triggered.connect(lambda: self._docAction(nwDocAction.REPL_NEXT)) self.srcMenu.addAction(self.aReplaceNext) @@ -802,25 +807,25 @@ class GuiMainMenu(QMenuBar): """Assemble the Format menu. """ # Format - self.fmtMenu = self.addMenu("&Format") + self.fmtMenu = self.addMenu(self.tr("&Format")) # Format > Emphasis - self.aFmtEmph = QAction("Emphasis", self) - self.aFmtEmph.setStatusTip("Add emphasis to selected text (italic)") + self.aFmtEmph = QAction(self.tr("Emphasis"), self) + self.aFmtEmph.setStatusTip(self.tr("Add emphasis to selected text (italic)")) self.aFmtEmph.setShortcut("Ctrl+I") self.aFmtEmph.triggered.connect(lambda: self._docAction(nwDocAction.EMPH)) self.fmtMenu.addAction(self.aFmtEmph) # Format > Strong Emphasis - self.aFmtStrong = QAction("Strong Emphasis", self) - self.aFmtStrong.setStatusTip("Add strong emphasis to selected text (bold)") + self.aFmtStrong = QAction(self.tr("Strong Emphasis"), self) + self.aFmtStrong.setStatusTip(self.tr("Add strong emphasis to selected text (bold)")) self.aFmtStrong.setShortcut("Ctrl+B") self.aFmtStrong.triggered.connect(lambda: self._docAction(nwDocAction.STRONG)) self.fmtMenu.addAction(self.aFmtStrong) # Format > Strikethrough - self.aFmtStrike = QAction("Strikethrough", self) - self.aFmtStrike.setStatusTip("Add strikethrough to selected text") + self.aFmtStrike = QAction(self.tr("Strikethrough"), self) + self.aFmtStrike.setStatusTip(self.tr("Add strikethrough to selected text")) self.aFmtStrike.setShortcut("Ctrl+D") self.aFmtStrike.triggered.connect(lambda: self._docAction(nwDocAction.STRIKE)) self.fmtMenu.addAction(self.aFmtStrike) @@ -829,15 +834,15 @@ class GuiMainMenu(QMenuBar): self.fmtMenu.addSeparator() # Format > Double Quotes - self.aFmtDQuote = QAction("Wrap Double Quotes", self) - self.aFmtDQuote.setStatusTip("Wrap selected text in double quotes") + self.aFmtDQuote = QAction(self.tr("Wrap Double Quotes"), self) + self.aFmtDQuote.setStatusTip(self.tr("Wrap selected text in double quotes")) self.aFmtDQuote.setShortcut("Ctrl+\"") self.aFmtDQuote.triggered.connect(lambda: self._docAction(nwDocAction.D_QUOTE)) self.fmtMenu.addAction(self.aFmtDQuote) # Format > Single Quotes - self.aFmtSQuote = QAction("Wrap Single Quotes", self) - self.aFmtSQuote.setStatusTip("Wrap selected text in single quotes") + self.aFmtSQuote = QAction(self.tr("Wrap Single Quotes"), self) + self.aFmtSQuote.setStatusTip(self.tr("Wrap selected text in single quotes")) self.aFmtSQuote.setShortcut("Ctrl+'") self.aFmtSQuote.triggered.connect(lambda: self._docAction(nwDocAction.S_QUOTE)) self.fmtMenu.addAction(self.aFmtSQuote) @@ -846,43 +851,43 @@ class GuiMainMenu(QMenuBar): self.fmtMenu.addSeparator() # Format > Header 1 - self.aFmtHead1 = QAction("Header 1", self) - self.aFmtHead1.setStatusTip("Change the block format to Header 1") + self.aFmtHead1 = QAction(self.tr("Header 1"), self) + self.aFmtHead1.setStatusTip(self.tr("Change the block format to Header 1")) self.aFmtHead1.setShortcut("Ctrl+1") self.aFmtHead1.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H1)) self.fmtMenu.addAction(self.aFmtHead1) # Format > Header 2 - self.aFmtHead2 = QAction("Header 2", self) - self.aFmtHead2.setStatusTip("Change the block format to Header 2") + self.aFmtHead2 = QAction(self.tr("Header 2"), self) + self.aFmtHead2.setStatusTip(self.tr("Change the block format to Header 2")) self.aFmtHead2.setShortcut("Ctrl+2") self.aFmtHead2.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H2)) self.fmtMenu.addAction(self.aFmtHead2) # Format > Header 3 - self.aFmtHead3 = QAction("Header 3", self) - self.aFmtHead3.setStatusTip("Change the block format to Header 3") + self.aFmtHead3 = QAction(self.tr("Header 3"), self) + self.aFmtHead3.setStatusTip(self.tr("Change the block format to Header 3")) self.aFmtHead3.setShortcut("Ctrl+3") self.aFmtHead3.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H3)) self.fmtMenu.addAction(self.aFmtHead3) # Format > Header 4 - self.aFmtHead4 = QAction("Header 4", self) - self.aFmtHead4.setStatusTip("Change the block format to Header 4") + self.aFmtHead4 = QAction(self.tr("Header 4"), self) + self.aFmtHead4.setStatusTip(self.tr("Change the block format to Header 4")) self.aFmtHead4.setShortcut("Ctrl+4") self.aFmtHead4.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H4)) self.fmtMenu.addAction(self.aFmtHead4) # Format > Comment - self.aFmtComment = QAction("Comment", self) - self.aFmtComment.setStatusTip("Change the block format to comment") + self.aFmtComment = QAction(self.tr("Comment"), self) + self.aFmtComment.setStatusTip(self.tr("Change the block format to comment")) self.aFmtComment.setShortcut("Ctrl+/") self.aFmtComment.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_COM)) self.fmtMenu.addAction(self.aFmtComment) # Format > Remove Block Format - self.aFmtNoFormat = QAction("Remove Block Format", self) - self.aFmtNoFormat.setStatusTip("Strips block format") + self.aFmtNoFormat = QAction(self.tr("Remove Block Format"), self) + self.aFmtNoFormat.setStatusTip(self.tr("Strips block format")) self.aFmtNoFormat.setShortcuts(["Ctrl+0", "Ctrl+Shift+/"]) self.aFmtNoFormat.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_TXT)) self.fmtMenu.addAction(self.aFmtNoFormat) @@ -891,14 +896,16 @@ class GuiMainMenu(QMenuBar): self.fmtMenu.addSeparator() # Format > Replace Single Quotes - self.aFmtReplSng = QAction("Replace Single Quotes", self) - self.aFmtReplSng.setStatusTip("Replace all straight single quotes in selected text") + self.aFmtReplSng = QAction(self.tr("Replace Single Quotes"), self) + self.aFmtReplSng.setStatusTip( + self.tr("Replace all straight single quotes in selected text")) self.aFmtReplSng.triggered.connect(lambda: self._docAction(nwDocAction.REPL_SNG)) self.fmtMenu.addAction(self.aFmtReplSng) # Format > Replace Double Quotes - self.aFmtReplDbl = QAction("Replace Double Quotes", self) - self.aFmtReplDbl.setStatusTip("Replace all straight double quotes in selected text") + self.aFmtReplDbl = QAction(self.tr("Replace Double Quotes"), self) + self.aFmtReplDbl.setStatusTip( + self.tr("Replace all straight double quotes in selected text")) self.aFmtReplDbl.triggered.connect(lambda: self._docAction(nwDocAction.REPL_DBL)) self.fmtMenu.addAction(self.aFmtReplDbl) @@ -908,11 +915,11 @@ class GuiMainMenu(QMenuBar): """Assemble the Tools menu. """ # Tools - self.toolsMenu = self.addMenu("&Tools") + self.toolsMenu = self.addMenu(self.tr("&Tools")) # Tools > Check Spelling - self.aSpellCheck = QAction("Check Spelling", self) - self.aSpellCheck.setStatusTip("Toggle check spelling") + self.aSpellCheck = QAction(self.tr("Check Spelling"), self) + self.aSpellCheck.setStatusTip(self.tr("Toggle check spelling")) self.aSpellCheck.setCheckable(True) self.aSpellCheck.setChecked(self.theProject.spellCheck) self.aSpellCheck.triggered.connect(self._toggleSpellCheck) # triggered, not toggled! @@ -920,15 +927,15 @@ class GuiMainMenu(QMenuBar): self.toolsMenu.addAction(self.aSpellCheck) # Tools > Re-Run Spell Check - self.aReRunSpell = QAction("Re-Run Spell Check", self) - self.aReRunSpell.setStatusTip("Run the spell checker on current document") + self.aReRunSpell = QAction(self.tr("Re-Run Spell Check"), self) + self.aReRunSpell.setStatusTip(self.tr("Run the spell checker on current document")) self.aReRunSpell.setShortcut("F7") self.aReRunSpell.triggered.connect(lambda: self.theParent.docEditor.spellCheckDocument()) self.toolsMenu.addAction(self.aReRunSpell) # Tools > Project Word List - self.aEditWordList = QAction("Project Word List", self) - self.aEditWordList.setStatusTip("Edit the project's word list") + self.aEditWordList = QAction(self.tr("Project Word List"), self) + self.aEditWordList.setStatusTip(self.tr("Edit the project's word list")) self.aEditWordList.triggered.connect(lambda: self.theParent.showProjectWordListDialog()) self.toolsMenu.addAction(self.aEditWordList) @@ -936,22 +943,23 @@ class GuiMainMenu(QMenuBar): self.toolsMenu.addSeparator() # Tools > Rebuild Indices - self.aRebuildIndex = QAction("Rebuild Index", self) - self.aRebuildIndex.setStatusTip("Rebuild the tag indices and word counts") + self.aRebuildIndex = QAction(self.tr("Rebuild Index"), self) + self.aRebuildIndex.setStatusTip(self.tr("Rebuild the tag indices and word counts")) self.aRebuildIndex.setShortcut("F9") self.aRebuildIndex.triggered.connect(lambda: self.theParent.rebuildIndex()) self.toolsMenu.addAction(self.aRebuildIndex) # Tools > Rebuild Outline - self.aRebuildOutline = QAction("Rebuild Outline", self) - self.aRebuildOutline.setStatusTip("Rebuild the novel outline tree") + self.aRebuildOutline = QAction(self.tr("Rebuild Outline"), self) + self.aRebuildOutline.setStatusTip(self.tr("Rebuild the novel outline tree")) self.aRebuildOutline.setShortcut("F10") self.aRebuildOutline.triggered.connect(lambda: self.theParent.rebuildOutline()) self.toolsMenu.addAction(self.aRebuildOutline) # Tools > Toggle Auto Build Outline - self.aAutoOutline = QAction("Auto-Update Outline", self) - self.aAutoOutline.setStatusTip("Update project outline when a novel file is changed") + self.aAutoOutline = QAction(self.tr("Auto-Update Outline"), self) + self.aAutoOutline.setStatusTip(self.tr( + "Update project outline when a novel file is changed")) self.aAutoOutline.setCheckable(True) self.aAutoOutline.toggled.connect(self._toggleAutoOutline) self.aAutoOutline.setShortcut("Ctrl+F10") @@ -961,28 +969,28 @@ class GuiMainMenu(QMenuBar): self.toolsMenu.addSeparator() # Tools > Backup - self.aBackupProject = QAction("Backup Project Folder", self) - self.aBackupProject.setStatusTip("Backup Project") + self.aBackupProject = QAction(self.tr("Backup Project Folder"), self) + self.aBackupProject.setStatusTip(self.tr("Backup Project")) self.aBackupProject.triggered.connect(lambda: self.theProject.zipIt(True)) self.toolsMenu.addAction(self.aBackupProject) # Tools > Export Project - self.aBuildProject = QAction("Build Novel Project", self) - self.aBuildProject.setStatusTip("Launch the Build novel project tool") + self.aBuildProject = QAction(self.tr("Build Novel Project"), self) + self.aBuildProject.setStatusTip(self.tr("Launch the Build novel project tool")) self.aBuildProject.setShortcut("F5") self.aBuildProject.triggered.connect(lambda: self.theParent.showBuildProjectDialog()) self.toolsMenu.addAction(self.aBuildProject) # Tools > Writing Stats - self.aWritingStats = QAction("Writing Statistics", self) - self.aWritingStats.setStatusTip("Show the writing statistics dialog") + self.aWritingStats = QAction(self.tr("Writing Statistics"), self) + self.aWritingStats.setStatusTip(self.tr("Show the writing statistics dialog")) self.aWritingStats.setShortcut("F6") self.aWritingStats.triggered.connect(lambda: self.theParent.showWritingStatsDialog()) self.toolsMenu.addAction(self.aWritingStats) # Tools > Settings - self.aPreferences = QAction("Preferences", self) - self.aPreferences.setStatusTip("Preferences") + self.aPreferences = QAction(self.tr("Preferences"), self) + self.aPreferences.setStatusTip(self.tr("Preferences")) self.aPreferences.setShortcut("Ctrl+,") self.aPreferences.setMenuRole(QAction.PreferencesRole) self.aPreferences.triggered.connect(lambda: self.theParent.showPreferencesDialog()) @@ -994,18 +1002,18 @@ class GuiMainMenu(QMenuBar): """Assemble the Help menu. """ # Help - self.helpMenu = self.addMenu("&Help") + self.helpMenu = self.addMenu(self.tr("&Help")) # Help > About - self.aAboutNW = QAction("About novelWriter", self) - self.aAboutNW.setStatusTip("About novelWriter") + self.aAboutNW = QAction(self.tr("About novelWriter"), self) + self.aAboutNW.setStatusTip(self.tr("About novelWriter")) self.aAboutNW.setMenuRole(QAction.AboutRole) self.aAboutNW.triggered.connect(lambda: self.theParent.showAboutNWDialog()) self.helpMenu.addAction(self.aAboutNW) # Help > About Qt5 - self.aAboutQt = QAction("About Qt5", self) - self.aAboutQt.setStatusTip("About Qt5") + self.aAboutQt = QAction(self.tr("About Qt5"), self) + self.aAboutQt.setStatusTip(self.tr("About Qt5")) self.aAboutQt.setMenuRole(QAction.AboutQtRole) self.aAboutQt.triggered.connect(lambda: self.theParent.showAboutQtDialog()) self.helpMenu.addAction(self.aAboutQt) @@ -1015,14 +1023,15 @@ class GuiMainMenu(QMenuBar): # Document > Documentation if self.mainConf.hasHelp and self.mainConf.hasAssistant: - self.aHelpLoc = QAction("Documentation (Local)", self) - self.aHelpLoc.setStatusTip("View local documentation with Qt Assistant") + self.aHelpLoc = QAction(self.tr("Documentation (Local)"), self) + self.aHelpLoc.setStatusTip(self.tr("View local documentation with Qt Assistant")) self.aHelpLoc.triggered.connect(self._openAssistant) self.aHelpLoc.setShortcut("F1") self.helpMenu.addAction(self.aHelpLoc) - self.aHelpWeb = QAction("Documentation (Online)", self) - self.aHelpWeb.setStatusTip("View online documentation at %s" % nw.__docurl__) + self.aHelpWeb = QAction(self.tr("Documentation (Online)"), self) + self.aHelpWeb.setStatusTip( + self.tr("View online documentation at {0}").format(nw.__docurl__)) self.aHelpWeb.triggered.connect(lambda: self._openWebsite(nw.__docurl__)) if self.mainConf.hasHelp and self.mainConf.hasAssistant: self.aHelpWeb.setShortcut("Shift+F1") @@ -1034,26 +1043,30 @@ class GuiMainMenu(QMenuBar): self.helpMenu.addSeparator() # Document > Report an Issue - self.aIssue = QAction("Report an Issue (GitHub)", self) - self.aIssue.setStatusTip("Report a bug or issue on GitHub at %s" % nw.__issuesurl__) + self.aIssue = QAction(self.tr("Report an Issue (GitHub)"), self) + self.aIssue.setStatusTip( + self.tr("Report a bug or issue on GitHub at {0}").format(nw.__issuesurl__)) self.aIssue.triggered.connect(lambda: self._openWebsite(nw.__issuesurl__)) self.helpMenu.addAction(self.aIssue) # Document > Ask a Question - self.aQuestion = QAction("Ask a Question (GitHub)", self) - self.aQuestion.setStatusTip("Ask a question on GitHub at %s" % nw.__helpurl__) + self.aQuestion = QAction(self.tr("Ask a Question (GitHub)"), self) + self.aQuestion.setStatusTip( + self.tr("Ask a question on GitHub at {0}").format(nw.__helpurl__)) self.aQuestion.triggered.connect(lambda: self._openWebsite(nw.__helpurl__)) self.helpMenu.addAction(self.aQuestion) # Document > Latest Release - self.aRelease = QAction("Latest Release (GitHub)", self) - self.aRelease.setStatusTip("Open the Releases page on GitHub at %s" % nw.__releaseurl__) + self.aRelease = QAction(self.tr("Latest Release (GitHub)"), self) + self.aRelease.setStatusTip( + self.tr("Open the Releases page on GitHub at {0}").format(nw.__releaseurl__)) self.aRelease.triggered.connect(lambda: self._openWebsite(nw.__releaseurl__)) self.helpMenu.addAction(self.aRelease) # Document > Main Website - self.aWebsite = QAction("The novelWriter Website", self) - self.aWebsite.setStatusTip("Open the novelWriter website at %s" % nw.__url__) + self.aWebsite = QAction(self.tr("The novelWriter Website"), self) + self.aWebsite.setStatusTip( + self.tr("Open the novelWriter website at {0}").format(nw.__url__)) self.aWebsite.triggered.connect(lambda: self._openWebsite(nw.__url__)) self.helpMenu.addAction(self.aWebsite) diff --git a/nw/gui/noveltree.py b/nw/gui/noveltree.py index 0ceaf9ec..7dde6aeb 100644 --- a/nw/gui/noveltree.py +++ b/nw/gui/noveltree.py @@ -63,7 +63,11 @@ class GuiNovelTree(QTreeWidget): self.setIconSize(QSize(iPx, iPx)) self.setIndentation(iPx) self.setColumnCount(3) - self.setHeaderLabels(["Title", "Words", "POV"]) + self.setHeaderLabels([ + self.tr("Title"), + self.tr("Words"), + self.tr("POV") + ]) self.itemDoubleClicked.connect(self._treeDoubleClick) self.itemSelectionChanged.connect(self._itemSelected) self.setSelectionBehavior(QAbstractItemView.SelectRows) @@ -73,9 +77,9 @@ class GuiNovelTree(QTreeWidget): treeHeadItem = self.headerItem() treeHeadItem.setTextAlignment(self.C_WORDS, Qt.AlignRight) - treeHeadItem.setToolTip(self.C_TITLE, "Section title") - treeHeadItem.setToolTip(self.C_WORDS, "Word count") - treeHeadItem.setToolTip(self.C_POV, "Point-of-view character") + treeHeadItem.setToolTip(self.C_TITLE, self.tr("Section title")) + treeHeadItem.setToolTip(self.C_WORDS, self.tr("Word count")) + treeHeadItem.setToolTip(self.C_POV, self.tr("Point-of-view character")) treeHeader = self.header() treeHeader.setStretchLastSection(True) diff --git a/nw/gui/outline.py b/nw/gui/outline.py index 666c8026..fac7de53 100644 --- a/nw/gui/outline.py +++ b/nw/gui/outline.py @@ -29,7 +29,7 @@ import logging from time import time -from PyQt5.QtCore import Qt, QSize +from PyQt5.QtCore import QCoreApplication, Qt, QSize from PyQt5.QtWidgets import ( QTreeWidget, QTreeWidgetItem, QMenu, QAction, QAbstractItemView ) @@ -149,7 +149,8 @@ class GuiOutline(QTreeWidget): """ self.clear() self.setColumnCount(1) - self.setHeaderLabel(nwLabels.OUTLINE_COLS[nwOutline.TITLE]) + self.setHeaderLabel( + QCoreApplication.translate("Constant", nwLabels.OUTLINE_COLS[nwOutline.TITLE])) self.treeOrder = [] self.colWidth = {} @@ -355,7 +356,8 @@ class GuiOutline(QTreeWidget): if self.firstView: theLabels = [] for i, hItem in enumerate(self.treeOrder): - theLabels.append(nwLabels.OUTLINE_COLS[hItem]) + theLabels.append( + QCoreApplication.translate("Constant", nwLabels.OUTLINE_COLS[hItem])) self.colIndex[hItem] = i self.setHeaderLabels(theLabels) @@ -474,7 +476,7 @@ class GuiOutlineHeaderMenu(QMenu): self.theParent = theParent self.acceptToggle = True - mnuHead = QAction("Select Columns", self) + mnuHead = QAction(self.tr("Select Columns"), self) self.addAction(mnuHead) self.addSeparator() @@ -482,7 +484,8 @@ class GuiOutlineHeaderMenu(QMenu): for hItem in nwOutline: if hItem == nwOutline.TITLE: continue - self.actionMap[hItem] = QAction(nwLabels.OUTLINE_COLS[hItem], self) + self.actionMap[hItem] = QAction( + QCoreApplication.translate("Constant", nwLabels.OUTLINE_COLS[hItem]), self) self.actionMap[hItem].setCheckable(True) self.actionMap[hItem].toggled.connect( lambda isChecked, tItem=hItem : self._columnToggled(isChecked, tItem) diff --git a/nw/gui/outlinedetails.py b/nw/gui/outlinedetails.py index 15785ff8..ab83ccee 100644 --- a/nw/gui/outlinedetails.py +++ b/nw/gui/outlinedetails.py @@ -27,7 +27,7 @@ along with this program. If not, see . import nw import logging -from PyQt5.QtCore import Qt +from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP, Qt from PyQt5.QtWidgets import ( QScrollArea, QWidget, QGridLayout, QHBoxLayout, QGroupBox, QLabel ) @@ -40,10 +40,10 @@ logger = logging.getLogger(__name__) class GuiOutlineDetails(QScrollArea): LVL_MAP = { - "H1" : "Title", - "H2" : "Chapter", - "H3" : "Scene", - "H4" : "Section" + "H1" : QT_TRANSLATE_NOOP("GuiOutlineDetails", "Title"), + "H2" : QT_TRANSLATE_NOOP("GuiOutlineDetails", "Chapter"), + "H3" : QT_TRANSLATE_NOOP("GuiOutlineDetails", "Scene"), + "H4" : QT_TRANSLATE_NOOP("GuiOutlineDetails", "Section"), } def __init__(self, theParent): @@ -66,9 +66,9 @@ class GuiOutlineDetails(QScrollArea): vSpace = int(self.mainConf.pxInt(4)) # Details Area - self.titleLabel = QLabel("Title") - self.fileLabel = QLabel("Document") - self.itemLabel = QLabel("Status") + self.titleLabel = QLabel("%s" % self.tr("Title")) + self.fileLabel = QLabel("%s" % self.tr("Document")) + self.itemLabel = QLabel("%s" % self.tr("Status")) self.titleValue = QLabel("") self.fileValue = QLabel("") self.itemValue = QLabel("") @@ -81,9 +81,9 @@ class GuiOutlineDetails(QScrollArea): self.itemValue.setMaximumWidth(maxTitle) # Stats Area - self.cCLabel = QLabel("Characters") - self.wCLabel = QLabel("Words") - self.pCLabel = QLabel("Paragraphs") + self.cCLabel = QLabel("%s" % self.tr("Characters")) + self.wCLabel = QLabel("%s" % self.tr("Words")) + self.pCLabel = QLabel("%s" % self.tr("Paragraphs")) self.cCValue = QLabel("") self.wCValue = QLabel("") self.pCValue = QLabel("") @@ -96,7 +96,7 @@ class GuiOutlineDetails(QScrollArea): self.pCValue.setAlignment(Qt.AlignRight) # Synopsis - self.synopLabel = QLabel("Synopsis") + self.synopLabel = QLabel("%s" % self.tr("Synopsis")) self.synopValue = QLabel("") self.synopLWrap = QHBoxLayout() self.synopValue.setWordWrap(True) @@ -104,15 +104,24 @@ class GuiOutlineDetails(QScrollArea): self.synopLWrap.addWidget(self.synopValue, 1) # Tags - self.povKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.POV_KEY]) - self.focKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY]) - self.chrKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.CHAR_KEY]) - self.pltKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY]) - self.timKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.TIME_KEY]) - self.wldKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.WORLD_KEY]) - self.objKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.OBJECT_KEY]) - self.entKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.ENTITY_KEY]) - self.cstKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.CUSTOM_KEY]) + self.povKeyLabel = QLabel("%s" % QCoreApplication.translate( + "Constant", nwLabels.KEY_NAME[nwKeyWords.POV_KEY])) + self.focKeyLabel = QLabel("%s" % QCoreApplication.translate( + "Constant", nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY])) + self.chrKeyLabel = QLabel("%s" % QCoreApplication.translate( + "Constant", nwLabels.KEY_NAME[nwKeyWords.CHAR_KEY])) + self.pltKeyLabel = QLabel("%s" % QCoreApplication.translate( + "Constant", nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY])) + self.timKeyLabel = QLabel("%s" % QCoreApplication.translate( + "Constant", nwLabels.KEY_NAME[nwKeyWords.TIME_KEY])) + self.wldKeyLabel = QLabel("%s" % QCoreApplication.translate( + "Constant", nwLabels.KEY_NAME[nwKeyWords.WORLD_KEY])) + self.objKeyLabel = QLabel("%s" % QCoreApplication.translate( + "Constant", nwLabels.KEY_NAME[nwKeyWords.OBJECT_KEY])) + self.entKeyLabel = QLabel("%s" % QCoreApplication.translate( + "Constant", nwLabels.KEY_NAME[nwKeyWords.ENTITY_KEY])) + self.cstKeyLabel = QLabel("%s" % QCoreApplication.translate( + "Constant", nwLabels.KEY_NAME[nwKeyWords.CUSTOM_KEY])) self.povKeyLWrap = QHBoxLayout() self.focKeyLWrap = QHBoxLayout() @@ -165,7 +174,7 @@ class GuiOutlineDetails(QScrollArea): self.cstKeyLWrap.addWidget(self.cstKeyValue, 1) # Selected Item Details - self.mainGroup = QGroupBox("Title Details", self) + self.mainGroup = QGroupBox(self.tr("Title Details"), self) self.mainForm = QGridLayout() self.mainGroup.setLayout(self.mainForm) @@ -190,7 +199,7 @@ class GuiOutlineDetails(QScrollArea): self.mainForm.setVerticalSpacing(vSpace) # Selected Item Tags - self.tagsGroup = QGroupBox("Reference Tags", self) + self.tagsGroup = QGroupBox(self.tr("Reference Tags"), self) self.tagsForm = QGridLayout() self.tagsGroup.setLayout(self.tagsForm) @@ -256,7 +265,7 @@ class GuiOutlineDetails(QScrollArea): def clearDetails(self): """Clear all the data labels. """ - self.titleLabel.setText("Title") + self.titleLabel.setText("%s" % self.tr("Title")) self.titleValue.setText("") self.fileValue.setText("") self.itemValue.setText("") @@ -286,9 +295,9 @@ class GuiOutlineDetails(QScrollArea): return False if novIdx["level"] in self.LVL_MAP: - self.titleLabel.setText("%s" % self.LVL_MAP[novIdx["level"]]) + self.titleLabel.setText("%s" % self.tr(self.LVL_MAP[novIdx["level"]])) else: - self.titleLabel.setText("Title") + self.titleLabel.setText("%s" % self.tr("Title")) self.titleValue.setText(novIdx["title"]) self.fileValue.setText(nwItem.itemName) diff --git a/nw/gui/preferences.py b/nw/gui/preferences.py index 4913be25..e2ddfaae 100644 --- a/nw/gui/preferences.py +++ b/nw/gui/preferences.py @@ -28,7 +28,7 @@ import nw import logging import os -from PyQt5.QtCore import Qt +from PyQt5.QtCore import QCoreApplication, Qt from PyQt5.QtGui import QFont from PyQt5.QtWidgets import ( QDialog, QWidget, QComboBox, QSpinBox, QPushButton, QDialogButtonBox, @@ -53,7 +53,7 @@ class GuiPreferences(PagedDialog): self.theParent = theParent self.theProject = theProject - self.setWindowTitle("Preferences") + self.setWindowTitle(self.tr("Preferences")) self.tabGeneral = GuiPreferencesGeneral(self.theParent) self.tabProjects = GuiPreferencesProjects(self.theParent) @@ -62,14 +62,16 @@ class GuiPreferences(PagedDialog): self.tabSyntax = GuiPreferencesSyntax(self.theParent) self.tabAuto = GuiPreferencesAutomation(self.theParent) - self.addTab(self.tabGeneral, "General") - self.addTab(self.tabProjects, "Projects") - self.addTab(self.tabDocs, "Documents") - self.addTab(self.tabEditor, "Editor") - self.addTab(self.tabSyntax, "Highlighting") - self.addTab(self.tabAuto, "Automation") + self.addTab(self.tabGeneral, self.tr("General")) + self.addTab(self.tabProjects, self.tr("Projects")) + self.addTab(self.tabDocs, self.tr("Documents")) + self.addTab(self.tabEditor, self.tr("Editor")) + self.addTab(self.tabSyntax, self.tr("Highlighting")) + self.addTab(self.tabAuto, self.tr("Automation")) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("Ok")) + self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel")) self.buttonBox.accepted.connect(self._doSave) self.buttonBox.rejected.connect(self._doClose) self.addControls(self.buttonBox) @@ -98,7 +100,7 @@ class GuiPreferences(PagedDialog): if needsRestart: self.theParent.makeAlert( - "Some changes will not be applied until novelWriter has been restarted.", + self.tr("Some changes will not be applied until novelWriter has been restarted."), nwAlert.INFO ) @@ -130,7 +132,7 @@ class GuiPreferencesGeneral(QWidget): # Look and Feel # ============= - self.mainForm.addGroupLabel("Look and Feel") + self.mainForm.addGroupLabel(self.tr("Look and Feel")) ## Select Theme self.guiTheme = QComboBox() @@ -143,9 +145,9 @@ class GuiPreferencesGeneral(QWidget): self.guiTheme.setCurrentIndex(themeIdx) self.mainForm.addRow( - "Main GUI theme", + self.tr("Main GUI theme"), self.guiTheme, - "Changing this requires restarting novelWriter." + self.tr("Changing this requires restarting novelWriter.") ) ## Select Icon Theme @@ -159,18 +161,18 @@ class GuiPreferencesGeneral(QWidget): self.guiIcons.setCurrentIndex(iconIdx) self.mainForm.addRow( - "Main icon theme", + self.tr("Main icon theme"), self.guiIcons, - "Changing this requires restarting novelWriter." + self.tr("Changing this requires restarting novelWriter.") ) ## Dark Icons self.guiDark = QSwitch() self.guiDark.setChecked(self.mainConf.guiDark) self.mainForm.addRow( - "Prefer icons for dark backgrounds", + self.tr("Prefer icons for dark backgrounds"), self.guiDark, - "May improve the look of icons on dark themes." + self.tr("May improve the look of icons on dark themes.") ) ## Font Family @@ -182,9 +184,9 @@ class GuiPreferencesGeneral(QWidget): self.fontButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("..."))) self.fontButton.clicked.connect(self._selectFont) self.mainForm.addRow( - "Font family", + self.tr("Font family"), self.guiFont, - "Changing this requires restarting novelWriter.", + self.tr("Changing this requires restarting novelWriter."), theButton = self.fontButton ) @@ -195,38 +197,38 @@ class GuiPreferencesGeneral(QWidget): self.guiFontSize.setSingleStep(1) self.guiFontSize.setValue(self.mainConf.guiFontSize) self.mainForm.addRow( - "Font size", + self.tr("Font size"), self.guiFontSize, - "Changing this requires restarting novelWriter.", + self.tr("Changing this requires restarting novelWriter."), theUnit = "pt" ) # GUI Settings # ============ - self.mainForm.addGroupLabel("GUI Settings") + self.mainForm.addGroupLabel(self.tr("GUI Settings")) self.showFullPath = QSwitch() self.showFullPath.setChecked(self.mainConf.showFullPath) self.mainForm.addRow( - "Show full path in document header", + self.tr("Show full path in document header"), self.showFullPath, - "Add the parent folder names to the header." + self.tr("Add the parent folder names to the header.") ) self.hideVScroll = QSwitch() self.hideVScroll.setChecked(self.mainConf.hideVScroll) self.mainForm.addRow( - "Hide vertical scroll bars in main windows", + self.tr("Hide vertical scroll bars in main windows"), self.hideVScroll, - "Scrolling available with mouse wheel and keys only." + self.tr("Scrolling available with mouse wheel and keys only.") ) self.hideHScroll = QSwitch() self.hideHScroll.setChecked(self.mainConf.hideHScroll) self.mainForm.addRow( - "Hide horizontal scroll bars in main windows", + self.tr("Hide horizontal scroll bars in main windows"), self.hideHScroll, - "Scrolling available with mouse wheel and keys only." + self.tr("Scrolling available with mouse wheel and keys only.") ) return @@ -295,7 +297,7 @@ class GuiPreferencesProjects(QWidget): # Automatic Save # ============== - self.mainForm.addGroupLabel("Automatic Save") + self.mainForm.addGroupLabel(self.tr("Automatic Save")) ## Document Save Timer self.autoSaveDoc = QSpinBox(self) @@ -304,10 +306,10 @@ class GuiPreferencesProjects(QWidget): self.autoSaveDoc.setSingleStep(1) self.autoSaveDoc.setValue(self.mainConf.autoSaveDoc) self.mainForm.addRow( - "Save document interval", + self.tr("Save document interval"), self.autoSaveDoc, - "How often the open document is automatically saved.", - theUnit="seconds" + self.tr("How often the open document is automatically saved."), + theUnit=self.tr("seconds") ) ## Project Save Timer @@ -317,24 +319,24 @@ class GuiPreferencesProjects(QWidget): self.autoSaveProj.setSingleStep(1) self.autoSaveProj.setValue(self.mainConf.autoSaveProj) self.mainForm.addRow( - "Save project interval", + self.tr("Save project interval"), self.autoSaveProj, - "How often the open project is automatically saved.", - theUnit="seconds" + self.tr("How often the open project is automatically saved."), + theUnit=self.tr("seconds") ) # Project Backup # ============== - self.mainForm.addGroupLabel("Project Backup") + self.mainForm.addGroupLabel(self.tr("Project Backup")) ## Backup Path self.backupPath = self.mainConf.backupPath - self.backupGetPath = QPushButton("Browse") + self.backupGetPath = QPushButton(self.tr("Browse")) self.backupGetPath.clicked.connect(self._backupFolder) self.backupPathRow = self.mainForm.addRow( - "Backup storage location", + self.tr("Backup storage location"), self.backupGetPath, - "Path: %s" % self.backupPath + self.tr("{0}: {1}").format(self.tr("Path"), self.backupPath) ) ## Run when closing @@ -342,9 +344,9 @@ class GuiPreferencesProjects(QWidget): self.backupOnClose.setChecked(self.mainConf.backupOnClose) self.backupOnClose.toggled.connect(self._toggledBackupOnClose) self.mainForm.addRow( - "Run backup when the project is closed", + self.tr("Run backup when the project is closed"), self.backupOnClose, - "Can be overridden for individual projects in project settings." + self.tr("Can be overridden for individual projects in project settings.") ) ## Ask before backup @@ -353,22 +355,22 @@ class GuiPreferencesProjects(QWidget): self.askBeforeBackup.setChecked(self.mainConf.askBeforeBackup) self.askBeforeBackup.setEnabled(self.mainConf.backupOnClose) self.mainForm.addRow( - "Ask before running backup", + self.tr("Ask before running backup"), self.askBeforeBackup, - "If off, backups will run in the background." + self.tr("If off, backups will run in the background.") ) # Session Timer # ============= - self.mainForm.addGroupLabel("Session Timer") + self.mainForm.addGroupLabel(self.tr("Session Timer")) ## Pause when idle self.stopWhenIdle = QSwitch() self.stopWhenIdle.setChecked(self.mainConf.stopWhenIdle) self.mainForm.addRow( - "Pause the session timer when not writing", + self.tr("Pause the session timer when not writing"), self.stopWhenIdle, - "Also pauses when the application window does not have focus." + self.tr("Also pauses when the application window does not have focus.") ) ## Inactive time for idle @@ -379,10 +381,10 @@ class GuiPreferencesProjects(QWidget): self.userIdleTime.setDecimals(1) self.userIdleTime.setValue(self.mainConf.userIdleTime/60.0) self.mainForm.addRow( - "Editor inactive time before pausing timer", + self.tr("Editor inactive time before pausing timer"), self.userIdleTime, - "User activity includes typing and changing the content.", - theUnit="minutes" + self.tr("User activity includes typing and changing the content."), + theUnit=self.tr("minutes") ) return @@ -422,11 +424,12 @@ class GuiPreferencesProjects(QWidget): dlgOpt |= QFileDialog.ShowDirsOnly dlgOpt |= QFileDialog.DontUseNativeDialog newDir = QFileDialog.getExistingDirectory( - self, "Backup Directory", currDir, options=dlgOpt + self, self.tr("Backup Directory"), currDir, options=dlgOpt ) if newDir: self.backupPath = newDir - self.mainForm.setHelpText(self.backupPathRow, "Path: %s" % self.backupPath) + self.mainForm.setHelpText( + self.backupPathRow, self.tr("{0}: {1}").format(self.tr("Path"), self.backupPath)) return True return False @@ -456,7 +459,7 @@ class GuiPreferencesDocuments(QWidget): # Text Style # ========== - self.mainForm.addGroupLabel("Text Style") + self.mainForm.addGroupLabel(self.tr("Text Style")) ## Font Family self.textFont = QLineEdit() @@ -467,9 +470,9 @@ class GuiPreferencesDocuments(QWidget): self.fontButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("..."))) self.fontButton.clicked.connect(self._selectFont) self.mainForm.addRow( - "Font family", + self.tr("Font family"), self.textFont, - "Font for the document editor and viewer.", + self.tr("Font for the document editor and viewer."), theButton = self.fontButton ) @@ -480,15 +483,15 @@ class GuiPreferencesDocuments(QWidget): self.textSize.setSingleStep(1) self.textSize.setValue(self.mainConf.textSize) self.mainForm.addRow( - "Font size", + self.tr("Font size"), self.textSize, - "Font size for the document editor and viewer.", + self.tr("Font size for the document editor and viewer."), theUnit = "pt" ) # Text Flow # ========= - self.mainForm.addGroupLabel("Text Flow") + self.mainForm.addGroupLabel(self.tr("Text Flow")) ## Max Text Width in Normal Mode self.textWidth = QSpinBox(self) @@ -497,10 +500,10 @@ class GuiPreferencesDocuments(QWidget): self.textWidth.setSingleStep(10) self.textWidth.setValue(self.mainConf.textWidth) self.mainForm.addRow( - "Maximum text width in \"Normal Mode\"", + self.tr("Maximum text width in \"Normal Mode\""), self.textWidth, - "Horizontal margins are scaled automatically.", - theUnit="px" + self.tr("Horizontal margins are scaled automatically."), + theUnit=self.tr("px") ) ## Max Text Width in Focus Mode @@ -510,37 +513,37 @@ class GuiPreferencesDocuments(QWidget): self.focusWidth.setSingleStep(10) self.focusWidth.setValue(self.mainConf.focusWidth) self.mainForm.addRow( - "Maximum text width in \"Focus Mode\"", + self.tr("Maximum text width in \"Focus Mode\""), self.focusWidth, - "Horizontal margins are scaled automatically.", - theUnit="px" + self.tr("Horizontal margins are scaled automatically."), + theUnit=self.tr("px") ) ## Document Fixed Width self.textFixedW = QSwitch() self.textFixedW.setChecked(not self.mainConf.textFixedW) self.mainForm.addRow( - "Disable maximum text width in \"Normal Mode\"", + self.tr("Disable maximum text width in \"Normal Mode\""), self.textFixedW, - "Text width is defined by the margins only." + self.tr("Text width is defined by the margins only.") ) ## Focus Mode Footer self.hideFocusFooter = QSwitch() self.hideFocusFooter.setChecked(self.mainConf.hideFocusFooter) self.mainForm.addRow( - "Hide document footer in \"Focus Mode\"", + self.tr("Hide document footer in \"Focus Mode\""), self.hideFocusFooter, - "Hide the information bar at the bottom of the document." + self.tr("Hide the information bar at the bottom of the document.") ) ## Justify Text self.doJustify = QSwitch() self.doJustify.setChecked(self.mainConf.doJustify) self.mainForm.addRow( - "Justify the text margins in editor and viewer", + self.tr("Justify the text margins in editor and viewer"), self.doJustify, - "Lay out text with straight edges in the editor and viewer." + self.tr("Lay out text with straight edges in the editor and viewer.") ) ## Document Margins @@ -550,10 +553,10 @@ class GuiPreferencesDocuments(QWidget): self.textMargin.setSingleStep(1) self.textMargin.setValue(self.mainConf.textMargin) self.mainForm.addRow( - "Text margin", + self.tr("Text margin"), self.textMargin, - "If maximum width is set, this becomes the minimum margin.", - theUnit="px" + self.tr("If maximum width is set, this becomes the minimum margin."), + theUnit=self.tr("px") ) ## Tab Width @@ -563,10 +566,10 @@ class GuiPreferencesDocuments(QWidget): self.tabWidth.setSingleStep(1) self.tabWidth.setValue(self.mainConf.tabWidth) self.mainForm.addRow( - "Tab width", + self.tr("Tab width"), self.tabWidth, - "The width of a tab key press in the editor and viewer.", - theUnit="px" + self.tr("The width of a tab key press in the editor and viewer."), + theUnit=self.tr("px") ) return @@ -626,13 +629,17 @@ class GuiPreferencesEditor(QWidget): # Spell Checking # ============== - self.mainForm.addGroupLabel("Spell Checking") + self.mainForm.addGroupLabel(self.tr("Spell Checking")) ## Spell Check Provider and Language self.spellLangList = QComboBox(self) self.spellToolList = QComboBox(self) - self.spellToolList.addItem("Internal (difflib)", nwConst.SP_INTERNAL) - self.spellToolList.addItem("Spell Enchant (pyenchant)", nwConst.SP_ENCHANT) + self.spellToolList.addItem( + self.tr("{0} ({1})").format(self.tr("Internal"), "difflib"), + QCoreApplication.translate("Constant", nwConst.SP_INTERNAL)) + self.spellToolList.addItem( + self.tr("{0} ({1})").format(self.tr("Spell Enchant"), "pyenchant"), + QCoreApplication.translate("Constant", nwConst.SP_ENCHANT)) theModel = self.spellToolList.model() idEnchant = self.spellToolList.findData(nwConst.SP_ENCHANT) @@ -645,14 +652,14 @@ class GuiPreferencesEditor(QWidget): self._doUpdateSpellTool(0) self.mainForm.addRow( - "Spell check provider", + self.tr("Spell check provider"), self.spellToolList, - "Note that the internal spell check tool is quite slow." + self.tr("Note that the internal spell check tool is quite slow.") ) self.mainForm.addRow( - "Spell check language", + self.tr("Spell check language"), self.spellLangList, - "Available languages are determined by your system." + self.tr("Available languages are determined by your system.") ) ## Big Document Size Limit @@ -662,15 +669,15 @@ class GuiPreferencesEditor(QWidget): self.bigDocLimit.setSingleStep(10) self.bigDocLimit.setValue(self.mainConf.bigDocLimit) self.mainForm.addRow( - "Big document limit", + self.tr("Big document limit"), self.bigDocLimit, - "Full spell checking is disabled above this limit.", - theUnit="kB" + self.tr("Full spell checking is disabled above this limit."), + theUnit=self.tr("kB") ) # Word Count # ========== - self.mainForm.addGroupLabel("Word Count") + self.mainForm.addGroupLabel(self.tr("Word Count")) ## Word Count Timer self.wordCountTimer = QDoubleSpinBox(self) @@ -680,54 +687,54 @@ class GuiPreferencesEditor(QWidget): self.wordCountTimer.setSingleStep(0.1) self.wordCountTimer.setValue(self.mainConf.wordCountTimer) self.mainForm.addRow( - "Word count interval", + self.tr("Word count interval"), self.wordCountTimer, - "How often the word count is updated.", - theUnit="seconds" + self.tr("How often the word count is updated."), + theUnit=self.tr("seconds") ) # Writing Guides # ============== - self.mainForm.addGroupLabel("Writing Guides") + self.mainForm.addGroupLabel(self.tr("Writing Guides")) ## Show Tabs and Spaces self.showTabsNSpaces = QSwitch() self.showTabsNSpaces.setChecked(self.mainConf.showTabsNSpaces) self.mainForm.addRow( - "Show tabs and spaces", + self.tr("Show tabs and spaces"), self.showTabsNSpaces, - "Add symbols to indicate tabs and spaces in the editor." + self.tr("Add symbols to indicate tabs and spaces in the editor.") ) ## Show Line Endings self.showLineEndings = QSwitch() self.showLineEndings.setChecked(self.mainConf.showLineEndings) self.mainForm.addRow( - "Show line endings", + self.tr("Show line endings"), self.showLineEndings, - "Add a symbol to indicate line endings in the editor." + self.tr("Add a symbol to indicate line endings in the editor.") ) # Scroll Behaviour # ================ - self.mainForm.addGroupLabel("Scroll Behaviour") + self.mainForm.addGroupLabel(self.tr("Scroll Behaviour")) ## Scroll Past End self.scrollPastEnd = QSwitch() self.scrollPastEnd.setChecked(self.mainConf.scrollPastEnd) self.mainForm.addRow( - "Scroll past end of the document", + self.tr("Scroll past end of the document"), self.scrollPastEnd, - "Also improves trypewriter scrolling for short documents." + self.tr("Also improves trypewriter scrolling for short documents.") ) ## Typewriter Scrolling self.autoScroll = QSwitch() self.autoScroll.setChecked(self.mainConf.autoScroll) self.mainForm.addRow( - "Typewriter style scrolling when you type", + self.tr("Typewriter style scrolling when you type"), self.autoScroll, - "Try to keep the cursor at a fixed vertical position." + self.tr("Try to keep the cursor at a fixed vertical position.") ) ## Typewriter Position @@ -737,9 +744,9 @@ class GuiPreferencesEditor(QWidget): self.autoScrollPos.setSingleStep(1) self.autoScrollPos.setValue(int(self.mainConf.autoScrollPos)) self.mainForm.addRow( - "Minimum position for Typewriter scrolling", + self.tr("Minimum position for Typewriter scrolling"), self.autoScrollPos, - "Percentage of the editor height from the top.", + self.tr("Percentage of the editor height from the top."), theUnit = "%" ) @@ -819,7 +826,7 @@ class GuiPreferencesSyntax(QWidget): # Highlighting Theme # ================== - self.mainForm.addGroupLabel("Highlighting Theme") + self.mainForm.addGroupLabel(self.tr("Highlighting Theme")) self.guiSyntax = QComboBox() self.guiSyntax.setMinimumWidth(self.mainConf.pxInt(200)) @@ -831,50 +838,50 @@ class GuiPreferencesSyntax(QWidget): self.guiSyntax.setCurrentIndex(syntaxIdx) self.mainForm.addRow( - "Highlighting theme", + self.tr("Highlighting theme"), self.guiSyntax, - "Colour theme to apply to the editor and viewer." + self.tr("Colour theme to apply to the editor and viewer.") ) # Quotes & Dialogue # ================= - self.mainForm.addGroupLabel("Quotes & Dialogue") + self.mainForm.addGroupLabel(self.tr("Quotes & Dialogue")) self.highlightQuotes = QSwitch() self.highlightQuotes.setChecked(self.mainConf.highlightQuotes) self.highlightQuotes.toggled.connect(self._toggleHighlightQuotes) self.mainForm.addRow( - "Highlight text wrapped in quotes", + self.tr("Highlight text wrapped in quotes"), self.highlightQuotes, - "Applies to single, double and straight quotes." + self.tr("Applies to single, double and straight quotes.") ) self.allowOpenSQuote = QSwitch() self.allowOpenSQuote.setChecked(self.mainConf.allowOpenSQuote) self.mainForm.addRow( - "Allow open-ended single quotes", + self.tr("Allow open-ended single quotes"), self.allowOpenSQuote, - "Highlight single-quoted line with no closing quote." + self.tr("Highlight single-quoted line with no closing quote.") ) self.allowOpenDQuote = QSwitch() self.allowOpenDQuote.setChecked(self.mainConf.allowOpenDQuote) self.mainForm.addRow( - "Allow open-ended double quotes", + self.tr("Allow open-ended double quotes"), self.allowOpenDQuote, - "Highlight double-quoted line with no closing quote." + self.tr("Highlight double-quoted line with no closing quote.") ) # Text Emphasis # ============= - self.mainForm.addGroupLabel("Text Emphasis") + self.mainForm.addGroupLabel(self.tr("Text Emphasis")) self.highlightEmph = QSwitch() self.highlightEmph.setChecked(self.mainConf.highlightEmph) self.mainForm.addRow( - "Add highlight colour to emphasised text", + self.tr("Add highlight colour to emphasised text"), self.highlightEmph, - "Applies to emphasis (italic) and strong (bold)." + self.tr("Applies to emphasis (italic) and strong (bold).") ) return @@ -927,15 +934,15 @@ class GuiPreferencesAutomation(QWidget): # Automatic Features # ================== - self.mainForm.addGroupLabel("Automatic Features") + self.mainForm.addGroupLabel(self.tr("Automatic Features")) ## Auto-Select Word Under Cursor self.autoSelect = QSwitch() self.autoSelect.setChecked(self.mainConf.autoSelect) self.mainForm.addRow( - "Auto-select word under cursor", + self.tr("Auto-select word under cursor"), self.autoSelect, - "Apply formatting to word under cursor if no selection is made." + self.tr("Apply formatting to word under cursor if no selection is made.") ) ## Auto-Replace as You Type Main Switch @@ -943,23 +950,23 @@ class GuiPreferencesAutomation(QWidget): self.doReplace.setChecked(self.mainConf.doReplace) self.doReplace.toggled.connect(self._toggleAutoReplaceMain) self.mainForm.addRow( - "Auto-replace text as you type", + self.tr("Auto-replace text as you type"), self.doReplace, - "Allow the editor to replace symbols as you type." + self.tr("Allow the editor to replace symbols as you type.") ) # Replace as You Type # =================== - self.mainForm.addGroupLabel("Replace as You Type") + self.mainForm.addGroupLabel(self.tr("Replace as You Type")) ## Auto-Replace Single Quotes self.doReplaceSQuote = QSwitch() self.doReplaceSQuote.setChecked(self.mainConf.doReplaceSQuote) self.doReplaceSQuote.setEnabled(self.mainConf.doReplace) self.mainForm.addRow( - "Auto-replace single quotes", + self.tr("Auto-replace single quotes"), self.doReplaceSQuote, - "Try to guess which is an opening or a closing single quote." + self.tr("Try to guess which is an opening or a closing single quote.") ) ## Auto-Replace Double Quotes @@ -967,9 +974,9 @@ class GuiPreferencesAutomation(QWidget): self.doReplaceDQuote.setChecked(self.mainConf.doReplaceDQuote) self.doReplaceDQuote.setEnabled(self.mainConf.doReplace) self.mainForm.addRow( - "Auto-replace double quotes", + self.tr("Auto-replace double quotes"), self.doReplaceDQuote, - "Try to guess which is an opening or a closing double quote." + self.tr("Try to guess which is an opening or a closing double quote.") ) ## Auto-Replace Hyphens @@ -977,9 +984,9 @@ class GuiPreferencesAutomation(QWidget): self.doReplaceDash.setChecked(self.mainConf.doReplaceDash) self.doReplaceDash.setEnabled(self.mainConf.doReplace) self.mainForm.addRow( - "Auto-replace dashes", + self.tr("Auto-replace dashes"), self.doReplaceDash, - "Double and triple hyphens become short and long dashes." + self.tr("Double and triple hyphens become short and long dashes.") ) ## Auto-Replace Dots @@ -987,14 +994,14 @@ class GuiPreferencesAutomation(QWidget): self.doReplaceDots.setChecked(self.mainConf.doReplaceDots) self.doReplaceDots.setEnabled(self.mainConf.doReplace) self.mainForm.addRow( - "Auto-replace dots", + self.tr("Auto-replace dots"), self.doReplaceDots, - "Three consecutive dots become ellipsis." + self.tr("Three consecutive dots become ellipsis.") ) # Quotation Style # =============== - self.mainForm.addGroupLabel("Quotation Style") + self.mainForm.addGroupLabel(self.tr("Quotation Style")) qWidth = self.mainConf.pxInt(40) bWidth = int(2.5*self.theTheme.getTextWidth("...")) @@ -1011,9 +1018,9 @@ class GuiPreferencesAutomation(QWidget): self.btnSingleStyleO.setMaximumWidth(bWidth) self.btnSingleStyleO.clicked.connect(lambda: self._getQuote("SO")) self.mainForm.addRow( - "Single quote open style", + self.tr("Single quote open style"), self.quoteSym["SO"], - "The symbol to use for a leading single quote.", + self.tr("The symbol to use for a leading single quote."), theButton=self.btnSingleStyleO ) @@ -1027,9 +1034,9 @@ class GuiPreferencesAutomation(QWidget): self.btnSingleStyleC.setMaximumWidth(bWidth) self.btnSingleStyleC.clicked.connect(lambda: self._getQuote("SC")) self.mainForm.addRow( - "Single quote close style", + self.tr("Single quote close style"), self.quoteSym["SC"], - "The symbol to use for a trailing single quote.", + self.tr("The symbol to use for a trailing single quote."), theButton=self.btnSingleStyleC ) @@ -1044,9 +1051,9 @@ class GuiPreferencesAutomation(QWidget): self.btnDoubleStyleO.setMaximumWidth(bWidth) self.btnDoubleStyleO.clicked.connect(lambda: self._getQuote("DO")) self.mainForm.addRow( - "Double quote open style", + self.tr("Double quote open style"), self.quoteSym["DO"], - "The symbol to use for a leading double quote.", + self.tr("The symbol to use for a leading double quote."), theButton=self.btnDoubleStyleO ) @@ -1060,9 +1067,9 @@ class GuiPreferencesAutomation(QWidget): self.btnDoubleStyleC.setMaximumWidth(bWidth) self.btnDoubleStyleC.clicked.connect(lambda: self._getQuote("DC")) self.mainForm.addRow( - "Double quote close style", + self.tr("Double quote close style"), self.quoteSym["DC"], - "The symbol to use for a trailing double quote.", + self.tr("The symbol to use for a trailing double quote."), theButton=self.btnDoubleStyleC ) diff --git a/nw/gui/projdetails.py b/nw/gui/projdetails.py index d37cebee..9e7e1029 100644 --- a/nw/gui/projdetails.py +++ b/nw/gui/projdetails.py @@ -54,7 +54,7 @@ class GuiProjectDetails(PagedDialog): self.theProject = theProject self.optState = theProject.optState - self.setWindowTitle("Project Details") + self.setWindowTitle(self.tr("Project Details")) wW = self.mainConf.pxInt(600) wH = self.mainConf.pxInt(400) @@ -69,10 +69,11 @@ class GuiProjectDetails(PagedDialog): self.tabMain = GuiProjectDetailsMain(self.theParent, self.theProject) self.tabContents = GuiProjectDetailsContents(self.theParent, self.theProject) - self.addTab(self.tabMain, "Overview") - self.addTab(self.tabContents, "Contents") + self.addTab(self.tabMain, self.tr("Overview")) + self.addTab(self.tabContents, self.tr("Contents")) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close) + self.buttonBox.button(QDialogButtonBox.Close).setText(self.tr("Close")) self.buttonBox.rejected.connect(self._doClose) self.addControls(self.buttonBox) @@ -154,7 +155,8 @@ class GuiProjectDetailsMain(QWidget): self.bookTitle.setAlignment(Qt.AlignHCenter) self.bookTitle.setWordWrap(True) - self.projName = QLabel("Working Title: %s" % self.theProject.projName) + self.projName = QLabel(self.tr("{0}: {1}").format( + self.tr("Working Title"), self.theProject.projName)) workFont = self.projName.font() workFont.setPointSizeF(0.8*fPt) workFont.setItalic(True) @@ -162,7 +164,7 @@ class GuiProjectDetailsMain(QWidget): self.projName.setAlignment(Qt.AlignHCenter) self.projName.setWordWrap(True) - self.bookAuthors = QLabel("By %s" % self.theProject.getAuthors()) + self.bookAuthors = QLabel(self.tr("By {0}").format(self.theProject.getAuthors())) authFont = self.bookAuthors.font() authFont.setPointSizeF(1.2*fPt) self.bookAuthors.setFont(authFont) @@ -175,20 +177,20 @@ class GuiProjectDetailsMain(QWidget): hCounts = self.theIndex.getNovelTitleCounts() nwCount = self.theIndex.getNovelWordCount() - self.wordCountLbl = QLabel("Words:") + self.wordCountLbl = QLabel("%s:" % self.tr("Words")) self.wordCountVal = QLabel(f"{nwCount:n}") - self.chapCountLbl = QLabel("Chapters:") + self.chapCountLbl = QLabel("%s:" % self.tr("Chapters")) self.chapCountVal = QLabel(f"{hCounts[2]:n}") - self.sceneCountLbl = QLabel("Scenes:") + self.sceneCountLbl = QLabel("%s:" % self.tr("Scenes")) self.sceneCountVal = QLabel(f"{hCounts[3]:n}") - self.revCountLbl = QLabel("Revisions:") + self.revCountLbl = QLabel("%s:" % self.tr("Revisions")) self.revCountVal = QLabel(f"{self.theProject.saveCount:n}") edTime = self.theProject.getCurrentEditTime() - self.editTimeLbl = QLabel("Editing Time:") + self.editTimeLbl = QLabel("%s:" % self.tr("Editing Time")) self.editTimeVal = QLabel(f"{edTime//3600:02d}:{edTime%3600//60:02d}") self.statsGrid = QGridLayout() @@ -208,7 +210,7 @@ class GuiProjectDetailsMain(QWidget): # Meta # ==== - self.projPathLbl = QLabel("Path:") + self.projPathLbl = QLabel("%s:" % self.tr("Path")) self.projPathVal = QLineEdit() self.projPathVal.setText(self.theProject.projPath) self.projPathVal.setReadOnly(True) @@ -271,7 +273,13 @@ class GuiProjectDetailsContents(QWidget): self.tocTree.setIndentation(0) self.tocTree.setColumnCount(6) self.tocTree.setSelectionMode(QAbstractItemView.NoSelection) - self.tocTree.setHeaderLabels(["Title", "Words", "Pages", "Page", "Progress", ""]) + self.tocTree.setHeaderLabels( + [self.tr("Title"), + self.tr("Words"), + self.tr("Pages"), + self.tr("Page"), + self.tr("Progress"), + ""]) treeHeadItem = self.tocTree.headerItem() treeHeadItem.setTextAlignment(self.C_WORDS, Qt.AlignRight) @@ -304,16 +312,16 @@ class GuiProjectDetailsContents(QWidget): clearDouble = self.optState.getInt("GuiProjectDetails", "clearDouble", True) wordsHelp = ( - "Typical word count for a 5 by 8 inch book page with 11 pt font is 350." + self.tr("Typical word count for a 5 by 8 inch book page with 11 pt font is 350.") ) offsetHelp = ( - "Start counting page numbers from this page." + self.tr("Start counting page numbers from this page.") ) dblHelp = ( - "Assume a new chapter or partition always start on an odd numbered page." + self.tr("Assume a new chapter or partition always start on an odd numbered page.") ) - self.wpLabel = QLabel("Words per page") + self.wpLabel = QLabel(self.tr("Words per page")) self.wpLabel.setToolTip(wordsHelp) self.wpValue = QSpinBox() @@ -324,7 +332,7 @@ class GuiProjectDetailsContents(QWidget): self.wpValue.setToolTip(wordsHelp) self.wpValue.valueChanged.connect(self._populateTree) - self.poLabel = QLabel("Count pages from") + self.poLabel = QLabel(self.tr("Count pages from")) self.poLabel.setToolTip(offsetHelp) self.poValue = QSpinBox() @@ -335,7 +343,7 @@ class GuiProjectDetailsContents(QWidget): self.poValue.setToolTip(offsetHelp) self.poValue.valueChanged.connect(self._populateTree) - self.dblLabel = QLabel("Clear double pages") + self.dblLabel = QLabel(self.tr("Clear double pages")) self.dblLabel.setToolTip(dblHelp) self.dblValue = QSwitch(self, 2*iPx, iPx) @@ -358,7 +366,7 @@ class GuiProjectDetailsContents(QWidget): # ======== self.outerBox = QVBoxLayout() - self.outerBox.addWidget(QLabel("Table of Contents")) + self.outerBox.addWidget(QLabel("%s" % self.tr("Table of Contents"))) self.outerBox.addWidget(self.tocTree) self.outerBox.addLayout(self.optionsBox) @@ -390,7 +398,7 @@ class GuiProjectDetailsContents(QWidget): """ self._theToC = [] self._theToC = self.theIndex.getTableOfContents(2) - self._theToC.append(("", 0, "END", 0)) + self._theToC.append(("", 0, self.tr("END"), 0)) return ## diff --git a/nw/gui/projload.py b/nw/gui/projload.py index 29401de3..8579d194 100644 --- a/nw/gui/projload.py +++ b/nw/gui/projload.py @@ -74,7 +74,7 @@ class GuiProjectLoad(QDialog): self.outerBox.setSpacing(sPx) self.innerBox.setSpacing(sPx) - self.setWindowTitle("Open Project") + self.setWindowTitle(self.tr("Open Project")) self.setMinimumWidth(self.mainConf.pxInt(650)) self.setMinimumHeight(self.mainConf.pxInt(400)) self.setModal(True) @@ -90,7 +90,11 @@ class GuiProjectLoad(QDialog): self.listBox.setSelectionMode(QAbstractItemView.SingleSelection) self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop) self.listBox.setColumnCount(3) - self.listBox.setHeaderLabels(["Working Title", "Words", "Last Opened"]) + self.listBox.setHeaderLabels([ + self.tr("Working Title"), + self.tr("Words"), + self.tr("Last Opened"), + ]) self.listBox.setRootIsDecorated(False) self.listBox.itemSelectionChanged.connect(self._doSelectRecent) self.listBox.itemDoubleClicked.connect(self._doOpenRecent) @@ -100,8 +104,8 @@ class GuiProjectLoad(QDialog): treeHead.setTextAlignment(self.C_COUNT, Qt.AlignRight) treeHead.setTextAlignment(self.C_TIME, Qt.AlignRight) - self.lblRecent = QLabel("Recently Opened Projects") - self.lblPath = QLabel("Path") + self.lblRecent = QLabel("%s" % self.tr("Recently Opened Projects")) + self.lblPath = QLabel("%s" % self.tr("Path")) self.selPath = QLineEdit("") self.selPath.setReadOnly(True) @@ -123,13 +127,15 @@ class GuiProjectLoad(QDialog): self.innerBox.addLayout(self.projectForm) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Open | QDialogButtonBox.Cancel) + self.buttonBox.button(QDialogButtonBox.Open).setText(self.tr("Open")) + self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel")) self.buttonBox.accepted.connect(self._doOpenRecent) self.buttonBox.rejected.connect(self._doCancel) - self.newButton = self.buttonBox.addButton("New", QDialogButtonBox.ActionRole) + self.newButton = self.buttonBox.addButton(self.tr("New"), QDialogButtonBox.ActionRole) self.newButton.clicked.connect(self._doNewProject) - self.delButton = self.buttonBox.addButton("Remove", QDialogButtonBox.ActionRole) + self.delButton = self.buttonBox.addButton(self.tr("Remove"), QDialogButtonBox.ActionRole) self.delButton.clicked.connect(self._doDeleteRecent) self.outerBox.addLayout(self.innerBox) @@ -183,8 +189,12 @@ class GuiProjectLoad(QDialog): dlgOpt = QFileDialog.Options() dlgOpt |= QFileDialog.DontUseNativeDialog projFile, _ = QFileDialog.getOpenFileName( - self, "Open novelWriter Project", "", - "novelWriter Project File (%s);;All Files (*)" % nwFiles.PROJ_FILE, + self, self.tr("Open novelWriter Project"), "", + ";;".join([ + self.tr("{0} ({1})").format( + self.tr("novelWriter Project File"), nwFiles.PROJ_FILE), + self.tr("{0} ({1})").format(self.tr("All Files"), "*") + ]), options=dlgOpt ) if projFile: @@ -221,10 +231,11 @@ class GuiProjectLoad(QDialog): selList = self.listBox.selectedItems() if selList: projName = selList[0].text(self.C_NAME) - msgYes = self.theParent.askQuestion("Remove Entry", ( - "Remove '%s' from the recent projects list? " - "The project files will not be deleted." - ) % projName) + msgYes = self.theParent.askQuestion( + self.tr("Remove Entry"), + self.tr("Remove '{0}' from the recent projects list? " + "The project files will not be deleted.").format(projName) + ) if msgYes: self.mainConf.removeFromRecentCache( selList[0].data(self.C_NAME, Qt.UserRole) diff --git a/nw/gui/projsettings.py b/nw/gui/projsettings.py index e6a4e485..71cc7043 100644 --- a/nw/gui/projsettings.py +++ b/nw/gui/projsettings.py @@ -54,7 +54,7 @@ class GuiProjectSettings(PagedDialog): self.optState = theProject.optState self.theProject.countStatus() - self.setWindowTitle("Project Settings") + self.setWindowTitle(self.tr("Project Settings")) wW = self.mainConf.pxInt(570) wH = self.mainConf.pxInt(375) @@ -71,12 +71,14 @@ class GuiProjectSettings(PagedDialog): self.tabImport = GuiProjectEditStatus(self.theParent, self.theProject, False) self.tabReplace = GuiProjectEditReplace(self.theParent, self.theProject) - self.addTab(self.tabMain, "Settings") - self.addTab(self.tabStatus, "Status") - self.addTab(self.tabImport, "Importance") - self.addTab(self.tabReplace, "Auto-Replace") + self.addTab(self.tabMain, self.tr("Settings")) + self.addTab(self.tabStatus, self.tr("Status")) + self.addTab(self.tabImport, self.tr("Importance")) + self.addTab(self.tabReplace, self.tr("Auto-Replace")) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.buttonBox.button(QDialogButtonBox.Ok).setText(self.tr("Ok")) + self.buttonBox.button(QDialogButtonBox.Cancel).setText(self.tr("Cancel")) self.buttonBox.accepted.connect(self._doSave) self.buttonBox.rejected.connect(self._doClose) self.addControls(self.buttonBox) @@ -166,7 +168,7 @@ class GuiProjectEditMain(QWidget): self.mainForm.setHelpTextStyle(self.theParent.theTheme.helpText) self.setLayout(self.mainForm) - self.mainForm.addGroupLabel("Project Settings") + self.mainForm.addGroupLabel(self.tr("Project Settings")) xW = self.mainConf.pxInt(250) xH = self.mainConf.pxInt(100) @@ -176,9 +178,9 @@ class GuiProjectEditMain(QWidget): self.editName.setFixedWidth(xW) self.editName.setText(self.theProject.projName) self.mainForm.addRow( - "Working title", + self.tr("Working title"), self.editName, - "Should be set only once." + self.tr("Should be set only once.") ) self.editTitle = QLineEdit() @@ -186,9 +188,9 @@ class GuiProjectEditMain(QWidget): self.editTitle.setFixedWidth(xW) self.editTitle.setText(self.theProject.bookTitle) self.mainForm.addRow( - "Novel title", + self.tr("Novel title"), self.editTitle, - "Change whenever you want!" + self.tr("Change whenever you want!") ) self.editAuthors = QPlainTextEdit() @@ -199,22 +201,22 @@ class GuiProjectEditMain(QWidget): self.editAuthors.setFixedHeight(xH) self.editAuthors.setFixedWidth(xW) self.mainForm.addRow( - "Author(s)", + self.tr("Author(s)"), self.editAuthors, - "One name per line." + self.tr("One name per line.") ) self.spellLang = QComboBox(self) theDict = self.theParent.docEditor.theDict - self.spellLang.addItem("Default", "None") + self.spellLang.addItem(self.tr("Default"), "None") if theDict is not None: for spTag, spName in theDict.listDictionaries(): self.spellLang.addItem(spName, spTag) self.mainForm.addRow( - "Spell check language", + self.tr("Spell check language"), self.spellLang, - "Overrides main preferences." + self.tr("Overrides main preferences.") ) spellIdx = 0 @@ -226,9 +228,9 @@ class GuiProjectEditMain(QWidget): self.doBackup = QSwitch(self) self.doBackup.setChecked(not self.theProject.doBackup) self.mainForm.addRow( - "No backup on close", + self.tr("No backup on close"), self.doBackup, - "Overrides main preferences." + self.tr("Overrides main preferences.") ) return @@ -271,12 +273,12 @@ class GuiProjectEditStatus(QWidget): self.editName = QLineEdit() self.editName.setMaxLength(40) self.editName.setEnabled(False) - self.newButton = QPushButton("New") - self.delButton = QPushButton("Delete") - self.saveButton = QPushButton("Save") + self.newButton = QPushButton(self.tr("New")) + self.delButton = QPushButton(self.tr("Delete")) + self.saveButton = QPushButton(self.tr("Save")) self.colPixmap = QPixmap(self.iPx, self.iPx) self.colPixmap.fill(QColor(120, 120, 120)) - self.colButton = QPushButton(QIcon(self.colPixmap), "Colour") + self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour")) self.colButton.setIconSize(self.colPixmap.rect().size()) self.newButton.clicked.connect(self._newItem) @@ -287,7 +289,7 @@ class GuiProjectEditStatus(QWidget): self.mainForm.addWidget(self.newButton) self.mainForm.addWidget(self.delButton) self.mainForm.addStretch(1) - self.mainForm.addWidget(QLabel("Name")) + self.mainForm.addWidget(QLabel("%s" % self.tr("Name"))) self.mainForm.addWidget(self.editName) self.mainForm.addWidget(self.colButton) self.mainForm.addStretch(1) @@ -297,9 +299,9 @@ class GuiProjectEditStatus(QWidget): self.mainBox.addLayout(self.mainForm) if isStatus: - self.outerBox.addWidget(QLabel("Novel File Status Levels")) + self.outerBox.addWidget(QLabel("%s" % self.tr("Novel File Status Levels"))) else: - self.outerBox.addWidget(QLabel("Note File Importance Levels")) + self.outerBox.addWidget(QLabel("%s" % self.tr("Note File Importance Levels"))) self.outerBox.addLayout(self.mainBox) self.setLayout(self.outerBox) @@ -325,7 +327,10 @@ class GuiProjectEditStatus(QWidget): """ if self.selColour is not None: newCol = QColorDialog.getColor( - self.selColour, self, "Select Colour", QColorDialog.DontUseNativeDialog + self.selColour, + self, + self.tr("Select Colour"), + QColorDialog.DontUseNativeDialog ) if newCol.isValid(): self.selColour = newCol @@ -338,7 +343,7 @@ class GuiProjectEditStatus(QWidget): def _newItem(self): """Create a new status item. """ - newItem = self._addItem("New Item", (0, 0, 0), None, 0) + newItem = self._addItem(self.tr("New Item"), (0, 0, 0), None, 0) newItem.setBackground(QBrush(QColor(0, 255, 0, 80))) self.colChanged = True return @@ -355,7 +360,7 @@ class GuiProjectEditStatus(QWidget): self.colChanged = True else: self.theParent.makeAlert( - "Cannot delete status item that is in use.", nwAlert.ERROR + self.tr("Cannot delete status item that is in use."), nwAlert.ERROR ) return @@ -372,7 +377,8 @@ class GuiProjectEditStatus(QWidget): self.selColour.blue(), self.colData[selIdx][4] ) - selItem.setText("%s [%d]" % (self.colData[selIdx][0], self.colCounts[selIdx])) + selItem.setText(self.tr("{0} [{1}]").format( + self.colData[selIdx][0], self.colCounts[selIdx])) selItem.setIcon(self.colButton.icon()) self.editName.setEnabled(False) self.colChanged = True @@ -384,7 +390,7 @@ class GuiProjectEditStatus(QWidget): newIcon = QPixmap(self.iPx, self.iPx) newIcon.fill(QColor(*iCol)) newItem = QListWidgetItem() - newItem.setText("%s [%d]" % (iName, nUse)) + newItem.setText(self.tr("{0} [{1}]").format(iName, nUse)) newItem.setIcon(QIcon(newIcon)) newItem.setData(Qt.UserRole, len(self.colData)) self.listBox.addItem(newItem) @@ -450,13 +456,16 @@ class GuiProjectEditReplace(QWidget): self.optState.getInt("GuiProjectSettings", "replaceColW", 100) ) self.listBox = QTreeWidget() - self.listBox.setHeaderLabels(["Keyword", "Replace With"]) + self.listBox.setHeaderLabels([ + self.tr("Keyword"), + self.tr("Replace With"), + ]) self.listBox.itemSelectionChanged.connect(self._selectedItem) self.listBox.setColumnWidth(0, wCol0) self.listBox.setIndentation(0) for aKey, aVal in self.theProject.autoReplace.items(): - newItem = QTreeWidgetItem(["<%s>" % aKey, aVal]) + newItem = QTreeWidgetItem([self.tr("<{0}>").format(aKey), aVal]) self.listBox.addTopLevelItem(newItem) self.listBox.sortByColumn(0, Qt.AscendingOrder) @@ -467,9 +476,9 @@ class GuiProjectEditReplace(QWidget): self.saveButton = QPushButton(self.theTheme.getIcon("done"), "") self.addButton = QPushButton(self.theTheme.getIcon("add"), "") self.delButton = QPushButton(self.theTheme.getIcon("remove"), "") - self.saveButton.setToolTip("Save entry") - self.addButton.setToolTip("Add new entry") - self.delButton.setToolTip("Delete selected entry") + self.saveButton.setToolTip(self.tr("Save entry")) + self.addButton.setToolTip(self.tr("Add new entry")) + self.delButton.setToolTip(self.tr("Delete selected entry")) self.editKey.setEnabled(False) self.editKey.setMaxLength(40) @@ -486,7 +495,8 @@ class GuiProjectEditReplace(QWidget): self.bottomBox.addWidget(self.addButton) self.bottomBox.addWidget(self.delButton) - self.outerBox.addWidget(QLabel("Text Replace List for Preview and Export")) + self.outerBox.addWidget( + QLabel("%s" % self.tr("Text Replace List for Preview and Export"))) self.outerBox.addWidget(self.listBox) self.outerBox.addLayout(self.bottomBox) self.setLayout(self.outerBox) @@ -538,7 +548,7 @@ class GuiProjectEditReplace(QWidget): saveKey = self._stripNotAllowed(newKey) if len(saveKey) > 0 and len(newVal) > 0: - selItem.setText(0, "<%s>" % saveKey) + selItem.setText(0, self.tr("<{0}>").format(saveKey)) selItem.setText(1, newVal) self.editKey.clear() self.editValue.clear() diff --git a/nw/gui/projtree.py b/nw/gui/projtree.py index eff1ae7c..02b8228c 100644 --- a/nw/gui/projtree.py +++ b/nw/gui/projtree.py @@ -30,7 +30,7 @@ import logging from time import time -from PyQt5.QtCore import Qt, QSize, pyqtSignal +from PyQt5.QtCore import QCoreApplication, Qt, QSize, pyqtSignal from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import ( QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMenu, QAction @@ -85,14 +85,19 @@ class GuiProjectTree(QTreeWidget): self.setExpandsOnDoubleClick(True) self.setIndentation(iPx) self.setColumnCount(4) - self.setHeaderLabels(["Label", "Words", "Inc", "Flags"]) + self.setHeaderLabels([ + self.tr("Label"), + self.tr("Words"), + self.tr("Inc"), + self.tr("Flags") + ]) treeHeadItem = self.headerItem() treeHeadItem.setTextAlignment(self.C_COUNT, Qt.AlignRight) - treeHeadItem.setToolTip(self.C_NAME, "Item label") - treeHeadItem.setToolTip(self.C_COUNT, "Word count") - treeHeadItem.setToolTip(self.C_EXPORT, "Include in build") - treeHeadItem.setToolTip(self.C_FLAGS, "Status, class, and layout flags") + treeHeadItem.setToolTip(self.C_NAME, self.tr("Item label")) + treeHeadItem.setToolTip(self.C_COUNT, self.tr("Word count")) + treeHeadItem.setToolTip(self.C_EXPORT, self.tr("Include in build")) + treeHeadItem.setToolTip(self.C_FLAGS, self.tr("Status, class, and layout flags")) # Let the last column stretch, and set the minimum size to the # size of the icon as the default Qt font metrics approach fails @@ -193,12 +198,12 @@ class GuiProjectTree(QTreeWidget): if itemClass is None: if itemType == nwItemType.FILE: self.makeAlert( - "Please select a valid location in the tree to add the document.", + self.tr("Please select a valid location in the tree to add the document."), nwAlert.ERROR ) else: self.makeAlert( - "Please select a valid location in the tree to add the folder.", + self.tr("Please select a valid location in the tree to add the folder."), nwAlert.ERROR ) return False @@ -209,7 +214,8 @@ class GuiProjectTree(QTreeWidget): ) if itemType == nwItemType.ROOT: - tHandle = self.theProject.newRoot(nwLabels.CLASS_NAME[itemClass], itemClass) + tHandle = self.theProject.newRoot( + QCoreApplication.translate("Constant", nwLabels.CLASS_NAME[itemClass]), itemClass) if tHandle is None: logger.error("No root item added") return False @@ -223,7 +229,7 @@ class GuiProjectTree(QTreeWidget): # If still nothing, give up if pHandle is None: self.makeAlert( - "Did not find anywhere to add the file or folder!", nwAlert.ERROR + self.tr("Did not find anywhere to add the file or folder!"), nwAlert.ERROR ) return False @@ -237,14 +243,15 @@ class GuiProjectTree(QTreeWidget): # If we again have no home, give up if pHandle is None: self.makeAlert( - "Did not find anywhere to add the file or folder!", nwAlert.ERROR + self.tr("Did not find anywhere to add the file or folder!"), nwAlert.ERROR ) return False if self.theProject.projTree.isTrashRoot(pHandle): self.makeAlert( - "Cannot add new files or folders to the %s folder." % ( - nwLabels.CLASS_NAME[nwItemClass.TRASH] + self.tr("Cannot add new files or folders to the {0} folder.").format( + QCoreApplication.translate( + "Constant", nwLabels.CLASS_NAME[nwItemClass.TRASH]) ), nwAlert.ERROR ) return False @@ -253,18 +260,18 @@ class GuiProjectTree(QTreeWidget): # If we're still here, add the file or folder if itemType == nwItemType.FILE: - tHandle = self.theProject.newFile("New File", itemClass, pHandle) + tHandle = self.theProject.newFile(self.tr("New File"), itemClass, pHandle) elif itemType == nwItemType.FOLDER: if len(parTree) >= nwConst.MAX_DEPTH - 1: # Folders cannot be deeper than MAX_DEPTH - 1, leaving room # for one more level of files. self.makeAlert(( - "Cannot add new folder to this item. " - "Maximum folder depth has been reached." + self.tr("Cannot add new folder to this item."), + self.tr("Maximum folder depth has been reached.") ), nwAlert.ERROR) return False - tHandle = self.theProject.newFolder("New Folder", itemClass, pHandle) + tHandle = self.theProject.newFolder(self.tr("New Folder"), itemClass, pHandle) else: logger.error("Failed to add new item") @@ -428,7 +435,7 @@ class GuiProjectTree(QTreeWidget): logger.debug("Emptying Trash folder") if trashHandle is None: self.makeAlert( - "There is currently no Trash folder in this project.", nwAlert.INFO + self.tr("There is currently no Trash folder in this project."), nwAlert.INFO ) return False @@ -438,11 +445,12 @@ class GuiProjectTree(QTreeWidget): nTrash = len(theTrash) if nTrash == 0: - self.makeAlert("The Trash folder is already empty.", nwAlert.INFO) + self.makeAlert(self.tr("The Trash folder is already empty."), nwAlert.INFO) return False msgYes = self.askQuestion( - "Empty Trash", "Permanently delete %d file(s) from Trash?" % nTrash + self.tr("Empty Trash"), + self.tr("Permanently delete {0} file(s) from Trash?").format(nTrash) ) if not msgYes: return False @@ -500,7 +508,8 @@ class GuiProjectTree(QTreeWidget): doPermanent = False if not alreadyAsked: msgYes = self.askQuestion( - "Delete File", "Permanently delete file '%s'?" % nwItemS.itemName + self.tr("Delete File"), + self.tr("Permanently delete file '{0}'?").format(nwItemS.itemName) ) if msgYes: doPermanent = True @@ -529,7 +538,8 @@ class GuiProjectTree(QTreeWidget): doTrash = False if askForTrash: msgYes = self.askQuestion( - "Delete File", "Move file '%s' to Trash?" % nwItemS.itemName + self.tr("Delete File"), + self.tr("Move file '{0}' to Trash?").format(nwItemS.itemName), ) if msgYes: doTrash = True @@ -564,9 +574,9 @@ class GuiProjectTree(QTreeWidget): self._setTreeChanged(True) else: self.makeAlert(( - "Cannot delete folder. It is not empty. " - "Recursive deletion is not supported. " - "Please delete the content first." + self.tr("Cannot delete folder. It is not empty."), + self.tr("Recursive deletion is not supported."), + self.tr("Please delete the content first."), ), nwAlert.ERROR) return False @@ -580,9 +590,9 @@ class GuiProjectTree(QTreeWidget): self._setTreeChanged(True) else: self.makeAlert(( - "Cannot delete root folder. It is not empty. " - "Recursive deletion is not supported. " - "Please delete the content first." + self.tr("Cannot delete root folder. It is not empty."), + self.tr("Recursive deletion is not supported."), + self.tr("Please delete the content first."), ), nwAlert.ERROR) return False @@ -835,7 +845,7 @@ class GuiProjectTree(QTreeWidget): snItem = self.theProject.projTree[sHandle] dnItem = self.theProject.projTree[dHandle] if dnItem is None: - self.makeAlert("The item cannot be moved to that location.", nwAlert.ERROR) + self.makeAlert(self.tr("The item cannot be moved to that location."), nwAlert.ERROR) return pItem = sItem.parent() @@ -866,7 +876,7 @@ class GuiProjectTree(QTreeWidget): else: theEvent.ignore() logger.debug("Drag'n'drop of item %s not accepted" % sHandle) - self.makeAlert("The item cannot be moved to that location.", nwAlert.ERROR) + self.makeAlert(self.tr("The item cannot be moved to that location."), nwAlert.ERROR) return @@ -964,7 +974,9 @@ class GuiProjectTree(QTreeWidget): self.addTopLevelItem(newItem) else: self.makeAlert( - "There is nowhere to add item with name '%s'" % nwItem.itemName, nwAlert.ERROR + self.tr("There is nowhere to add item with name '{0}'").format( + nwItem.itemName), + nwAlert.ERROR ) del self._treeMap[tHandle] return None @@ -1083,43 +1095,43 @@ class GuiProjectTreeMenu(QMenu): self.theTree = theTree self.theItem = None - self.editItem = QAction("Edit Project Item", self) + self.editItem = QAction(self.tr("Edit Project Item"), self) self.editItem.triggered.connect(self._doEditItem) self.addAction(self.editItem) - self.openItem = QAction("Open Document", self) + self.openItem = QAction(self.tr("Open Document"), self) self.openItem.triggered.connect(self._doOpenItem) self.addAction(self.openItem) - self.viewItem = QAction("View Document", self) + self.viewItem = QAction(self.tr("View Document"), self) self.viewItem.triggered.connect(self._doViewItem) self.addAction(self.viewItem) - self.toggleExp = QAction("Toggle Included Flag", self) + self.toggleExp = QAction(self.tr("Toggle Included Flag"), self) self.toggleExp.triggered.connect(self._doToggleExported) self.addAction(self.toggleExp) - self.newFile = QAction("New File", self) + self.newFile = QAction(self.tr("New File"), self) self.newFile.triggered.connect(self._doMakeFile) self.addAction(self.newFile) - self.newFolder = QAction("New Folder", self) + self.newFolder = QAction(self.tr("New Folder"), self) self.newFolder.triggered.connect(self._doMakeFolder) self.addAction(self.newFolder) - self.deleteItem = QAction("Delete Item", self) + self.deleteItem = QAction(self.tr("Delete Item"), self) self.deleteItem.triggered.connect(self._doDeleteItem) self.addAction(self.deleteItem) - self.emptyTrash = QAction("Empty Trash", self) + self.emptyTrash = QAction(self.tr("Empty Trash"), self) self.emptyTrash.triggered.connect(self._doEmptyTrash) self.addAction(self.emptyTrash) - self.moveUp = QAction("Move Item Up", self) + self.moveUp = QAction(self.tr("Move Item Up"), self) self.moveUp.triggered.connect(self._doMoveUp) self.addAction(self.moveUp) - self.moveDown = QAction("Move Item Down", self) + self.moveDown = QAction(self.tr("Move Item Down"), self) self.moveDown.triggered.connect(self._doMoveDown) self.addAction(self.moveDown) diff --git a/nw/gui/projwizard.py b/nw/gui/projwizard.py index b1d72520..fc854f16 100644 --- a/nw/gui/projwizard.py +++ b/nw/gui/projwizard.py @@ -28,7 +28,7 @@ import nw import logging import os -from PyQt5.QtCore import Qt +from PyQt5.QtCore import QCoreApplication, Qt from PyQt5.QtWidgets import ( QWizard, QWizardPage, QLabel, QVBoxLayout, QLineEdit, QPlainTextEdit, QPushButton, QFileDialog, QHBoxLayout, QRadioButton, QFormLayout, @@ -94,16 +94,19 @@ class ProjWizardIntroPage(QWizardPage): self.theWizard = theWizard self.theTheme = theWizard.theTheme - self.setTitle("Create New Project") + self.setTitle(self.tr("Create New Project")) self.theText = QLabel( - "Provide at least a working title. The working title should not " - "be change beyond this point as it is used by the application for " - "generating file names for for instance backups. The other fields " - "are optional and can be changed at any time in Project Settings." + self.tr("Provide at least a working title. The working title should not " + "be change beyond this point as it is used by the application for " + "generating file names for for instance backups. The other fields " + "are optional and can be changed at any time in Project Settings.") ) self.theText.setWordWrap(True) - self.imgCredit = QLabel("Side image by Peter Mitterhofer, CC BY-SA 4.0") + self.imgCredit = QLabel(self.tr("Side image by {author:s}, {license:s}").format( + author = "Peter Mitterhofer", + license = "CC BY-SA 4.0" + )) lblFont = self.imgCredit.font() lblFont.setPointSizeF(0.6*self.theTheme.fontPointSize) self.imgCredit.setFont(lblFont) @@ -117,22 +120,22 @@ class ProjWizardIntroPage(QWizardPage): self.projName = QLineEdit() self.projName.setMaxLength(200) self.projName.setFixedWidth(xW) - self.projName.setPlaceholderText("Required") + self.projName.setPlaceholderText(self.tr("Required")) self.projTitle = QLineEdit() self.projTitle.setMaxLength(200) self.projTitle.setFixedWidth(xW) - self.projTitle.setPlaceholderText("Optional") + self.projTitle.setPlaceholderText(self.tr("Optional")) self.projAuthors = QPlainTextEdit() self.projAuthors.setFixedHeight(xH) self.projAuthors.setFixedWidth(xW) - self.projAuthors.setPlaceholderText("Optional. One name per line.") + self.projAuthors.setPlaceholderText(self.tr("Optional. One name per line.")) self.mainForm = QFormLayout() - self.mainForm.addRow("Working Title", self.projName) - self.mainForm.addRow("Novel Title", self.projTitle) - self.mainForm.addRow("Author(s)", self.projAuthors) + self.mainForm.addRow(self.tr("Working Title"), self.projName) + self.mainForm.addRow(self.tr("Novel Title"), self.projTitle) + self.mainForm.addRow(self.tr("Author(s)"), self.projAuthors) self.mainForm.setVerticalSpacing(fS) self.registerField("projName*", self.projName) @@ -161,10 +164,10 @@ class ProjWizardFolderPage(QWizardPage): self.theWizard = theWizard self.theTheme = theWizard.theTheme - self.setTitle("Select Project Folder") + self.setTitle(self.tr("Select Project Folder")) self.theText = QLabel( - "Select a location to store the project. A new project folder " - "will be created in the selected location." + self.tr("Select a location to store the project. A new project folder " + "will be created in the selected location.") ) self.theText.setWordWrap(True) @@ -174,14 +177,14 @@ class ProjWizardFolderPage(QWizardPage): self.projPath = QLineEdit("") self.projPath.setFixedWidth(xW) - self.projPath.setPlaceholderText("Required") + self.projPath.setPlaceholderText(self.tr("Required")) self.browseButton = QPushButton("...") self.browseButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("..."))) self.browseButton.clicked.connect(self._doBrowse) self.mainForm = QHBoxLayout() - self.mainForm.addWidget(QLabel("Project Path"), 0) + self.mainForm.addWidget(QLabel(self.tr("Project Path")), 0) self.mainForm.addWidget(self.projPath, 1) self.mainForm.addWidget(self.browseButton, 0) self.mainForm.setSpacing(fS) @@ -213,7 +216,7 @@ class ProjWizardFolderPage(QWizardPage): dlgOpt |= QFileDialog.ShowDirsOnly dlgOpt |= QFileDialog.DontUseNativeDialog projDir = QFileDialog.getExistingDirectory( - self, "Select Project Folder", lastPath, options=dlgOpt + self, self.tr("Select Project Folder"), lastPath, options=dlgOpt ) if projDir: projName = self.field("projName") @@ -235,20 +238,20 @@ class ProjWizardPopulatePage(QWizardPage): self.mainConf = nw.CONFIG self.theWizard = theWizard - self.setTitle("Populate Project") + self.setTitle(self.tr("Populate Project")) self.theText = QLabel( - "Choose how to pre-fill the project. Either with a minimal set of " - "starter items, an example project explaining and showing many of " - "the features, or show further custom options on the next page." + self.tr("Choose how to pre-fill the project. Either with a minimal set of " + "starter items, an example project explaining and showing many of " + "the features, or show further custom options on the next page.") ) self.theText.setWordWrap(True) vS = self.mainConf.pxInt(12) fS = self.mainConf.pxInt(4) - self.popMinimal = QRadioButton("Fill the project with a minimal set of items") - self.popSample = QRadioButton("Fill the project with example files") - self.popCustom = QRadioButton("Show detailed options for filling the project") + self.popMinimal = QRadioButton(self.tr("Fill the project with a minimal set of items")) + self.popSample = QRadioButton(self.tr("Fill the project with example files")) + self.popCustom = QRadioButton(self.tr("Show detailed options for filling the project")) self.popMinimal.setChecked(True) self.popBox = QVBoxLayout() @@ -290,27 +293,33 @@ class ProjWizardCustomPage(QWizardPage): self.mainConf = nw.CONFIG self.theWizard = theWizard - self.setTitle("Custom Project Options") + self.setTitle(self.tr("Custom Project Options")) self.theText = QLabel( - "Select which additional root folders to make, and how to populate " - "the Novel folder. If you don't want to add chapters or scenes, set " - "the values to 0. You can add scenes without chapters." + self.tr("Select which additional root folders to make, and how to populate " + "the Novel folder. If you don't want to add chapters or scenes, set " + "the values to 0. You can add scenes without chapters.") ) self.theText.setWordWrap(True) vS = self.mainConf.pxInt(12) # Root Folders - self.rootGroup = QGroupBox("Additional Root Folders") + self.rootGroup = QGroupBox(self.tr("Additional Root Folders")) self.rootForm = QGridLayout() self.rootGroup.setLayout(self.rootForm) - self.lblPlot = QLabel("%s folder" % nwLabels.CLASS_NAME[nwItemClass.PLOT]) - self.lblChar = QLabel("%s folder" % nwLabels.CLASS_NAME[nwItemClass.CHARACTER]) - self.lblWorld = QLabel("%s folder" % nwLabels.CLASS_NAME[nwItemClass.WORLD]) - self.lblTime = QLabel("%s folder" % nwLabels.CLASS_NAME[nwItemClass.TIMELINE]) - self.lblObject = QLabel("%s folder" % nwLabels.CLASS_NAME[nwItemClass.OBJECT]) - self.lblEntity = QLabel("%s folder" % nwLabels.CLASS_NAME[nwItemClass.ENTITY]) + self.lblPlot = QLabel(self.tr("{0} folder").format( + QCoreApplication.translate("Constant", nwLabels.CLASS_NAME[nwItemClass.PLOT]))) + self.lblChar = QLabel(self.tr("{0} folder").format( + QCoreApplication.translate("Constant", nwLabels.CLASS_NAME[nwItemClass.CHARACTER]))) + self.lblWorld = QLabel(self.tr("{0} folder").format( + QCoreApplication.translate("Constant", nwLabels.CLASS_NAME[nwItemClass.WORLD]))) + self.lblTime = QLabel(self.tr("{0} folder").format( + QCoreApplication.translate("Constant", nwLabels.CLASS_NAME[nwItemClass.TIMELINE]))) + self.lblObject = QLabel(self.tr("{0} folder").format( + QCoreApplication.translate("Constant", nwLabels.CLASS_NAME[nwItemClass.OBJECT]))) + self.lblEntity = QLabel(self.tr("{0} folder").format( + QCoreApplication.translate("Constant", nwLabels.CLASS_NAME[nwItemClass.ENTITY]))) self.addPlot = QSwitch() self.addChar = QSwitch() @@ -338,7 +347,7 @@ class ProjWizardCustomPage(QWizardPage): self.rootForm.setRowStretch(6, 1) # Novel Options - self.novelGroup = QGroupBox("Populate Novel Folder") + self.novelGroup = QGroupBox(self.tr("Populate Novel Folder")) self.novelForm = QGridLayout() self.novelGroup.setLayout(self.novelForm) @@ -353,9 +362,9 @@ class ProjWizardCustomPage(QWizardPage): self.chFolders = QSwitch() self.chFolders.setChecked(True) - self.novelForm.addWidget(QLabel("Add chapters"), 0, 0) - self.novelForm.addWidget(QLabel("Scenes (per chapter)"), 1, 0) - self.novelForm.addWidget(QLabel("Add chapter folders"), 2, 0) + self.novelForm.addWidget(QLabel(self.tr("Add chapters")), 0, 0) + self.novelForm.addWidget(QLabel(self.tr("Scenes (per chapter)")), 1, 0) + self.novelForm.addWidget(QLabel(self.tr("Add chapter folders")), 2, 0) self.novelForm.addWidget(self.numChapters, 0, 1, 1, 1, Qt.AlignRight) self.novelForm.addWidget(self.numScenes, 1, 1, 1, 1, Qt.AlignRight) self.novelForm.addWidget(self.chFolders, 2, 1, 1, 1, Qt.AlignRight) @@ -396,13 +405,12 @@ class ProjWizardFinalPage(QWizardPage): self.mainConf = nw.CONFIG self.theWizard = theWizard - self.setTitle("Finished") - self.theText = QLabel(( - "

All done.

" - "

Press '{finish}' to create the new project.

" - ).format( - finish = "Done" if self.mainConf.osDarwin else "Finish" - )) + self.setTitle(self.tr("Finished")) + self.theText = QLabel("".join([ + ("

%s

" % self.tr("All done.")), + ("

%s

" % self.tr("Press '{0}' to create the new project.").format( + self.tr("Done") if self.mainConf.osDarwin else self.tr("Finish"))) + ])) self.theText.setWordWrap(True) # Assemble diff --git a/nw/gui/statusbar.py b/nw/gui/statusbar.py index 441cacda..617bbbbe 100644 --- a/nw/gui/statusbar.py +++ b/nw/gui/statusbar.py @@ -63,7 +63,7 @@ class GuiMainStatus(QStatusBar): ## The Spell Checker Language self.langIcon = QLabel("") - self.langText = QLabel("None") + self.langText = QLabel(self.tr("None")) self.langIcon.setPixmap(self.theTheme.getPixmap("status_lang", (iPx, iPx))) self.langIcon.setContentsMargins(0, 0, 0, 0) self.langText.setContentsMargins(0, 0, xM, 0) @@ -72,7 +72,7 @@ class GuiMainStatus(QStatusBar): ## The Editor Status self.docIcon = StatusLED(colNone, colTrue, colFalse, iPx, iPx, self) - self.docText = QLabel("Editor") + self.docText = QLabel(self.tr("Editor")) self.docIcon.setContentsMargins(0, 0, 0, 0) self.docText.setContentsMargins(0, 0, xM, 0) self.addPermanentWidget(self.docIcon) @@ -80,7 +80,7 @@ class GuiMainStatus(QStatusBar): ## The Project Status self.projIcon = StatusLED(colNone, colTrue, colFalse, iPx, iPx, self) - self.projText = QLabel("Project") + self.projText = QLabel(self.tr("Project")) self.projIcon.setContentsMargins(0, 0, 0, 0) self.projText.setContentsMargins(0, 0, xM, 0) self.addPermanentWidget(self.projIcon) @@ -103,7 +103,7 @@ class GuiMainStatus(QStatusBar): self.timeIcon = QLabel() self.timeText = QLabel("") self.timeIcon.setPixmap(self.timePixmap) - self.timeText.setToolTip("Session Time") + self.timeText.setToolTip(self.tr("Session Time")) self.timeText.setMinimumWidth(self.theTheme.getTextWidth("00:00:00:")) self.timeIcon.setContentsMargins(0, 0, 0, 0) self.timeText.setContentsMargins(0, 0, 0, 0) @@ -151,12 +151,14 @@ class GuiMainStatus(QStatusBar): """Set the language code for the spell checker. """ if theLanguage is None: - self.langText.setText("None") + self.langText.setText(self.tr("None")) self.langText.setToolTip("") else: self.langText.setText(NWSpellCheck.expandLanguage(theLanguage)) self.langText.setToolTip( - "Provider: %s" % (theProvider if theProvider else "unknown") + self.tr("{0}: {1}").format( + self.tr("Provider"), + theProvider if theProvider else self.tr("unknown")) ) return @@ -175,8 +177,8 @@ class GuiMainStatus(QStatusBar): def setStats(self, pWC, sWC): """Set the current project statistics. """ - self.statsText.setText(f"Words: {pWC:n} ({sWC:+n})") - self.statsText.setToolTip("Project word count (session change)") + self.statsText.setText("%s: %s (%s)" % (self.tr("Words"), f"{pWC:n}", f"{sWC:+n}")) + self.statsText.setToolTip(self.tr("Project word count (session change)")) return def setUserIdle(self, userIdle): diff --git a/nw/gui/theme.py b/nw/gui/theme.py index 9ec6018e..fc30a839 100644 --- a/nw/gui/theme.py +++ b/nw/gui/theme.py @@ -393,7 +393,7 @@ class GuiTheme: confParser.read_file(inFile) except Exception as e: self.theParent.makeAlert( - ["Could not load theme config file.", str(e)], nwAlert.ERROR + [self.tr("Could not load theme config file."), str(e)], nwAlert.ERROR ) continue themeName = "" @@ -426,7 +426,7 @@ class GuiTheme: confParser.read_file(inFile) except Exception as e: self.theParent.makeAlert( - ["Could not load syntax file.", str(e)], nwAlert.ERROR + [self.tr("Could not load syntax file."), str(e)], nwAlert.ERROR ) return [] syntaxName = "" @@ -741,7 +741,7 @@ class GuiIcons: confParser.read_file(inFile) except Exception as e: self.theParent.makeAlert( - ["Could not load theme config file.", str(e)], nwAlert.ERROR + [self.tr("Could not load theme config file."), str(e)], nwAlert.ERROR ) continue themeName = "" diff --git a/nw/gui/wordlist.py b/nw/gui/wordlist.py index f5b6fe80..0e7f3747 100644 --- a/nw/gui/wordlist.py +++ b/nw/gui/wordlist.py @@ -52,7 +52,7 @@ class GuiWordList(QDialog): self.theProject = theProject self.optState = theProject.optState - self.setWindowTitle("Project Word List") + self.setWindowTitle(self.tr("Project Word List")) mS = self.mainConf.pxInt(250) wW = self.mainConf.pxInt(320) @@ -68,7 +68,7 @@ class GuiWordList(QDialog): # Main Widgets # ============ - self.headLabel = QLabel("Project Word List") + self.headLabel = QLabel("%s" % self.tr("Project Word List")) self.listBox = QListWidget() self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop) @@ -77,11 +77,11 @@ class GuiWordList(QDialog): self.newEntry = QLineEdit() self.addButton = QPushButton(self.theTheme.getIcon("add"), "") - self.addButton.setToolTip("Add new entry") + self.addButton.setToolTip(self.tr("Add new entry")) self.addButton.clicked.connect(self._doAdd) self.delButton = QPushButton(self.theTheme.getIcon("remove"), "") - self.delButton.setToolTip("Delete selected entry") + self.delButton.setToolTip(self.tr("Delete selected entry")) self.delButton.clicked.connect(self._doDelete) self.editBox = QHBoxLayout() @@ -90,6 +90,8 @@ class GuiWordList(QDialog): self.editBox.addWidget(self.delButton, 0) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Close) + self.buttonBox.button(QDialogButtonBox.Save).setText(self.tr("Save")) + self.buttonBox.button(QDialogButtonBox.Close).setText(self.tr("Close")) self.buttonBox.accepted.connect(self._doSave) self.buttonBox.rejected.connect(self._doClose) @@ -121,12 +123,13 @@ class GuiWordList(QDialog): """ newWord = self.newEntry.text().strip() if newWord == "": - self.theParent.makeAlert("Cannot add a blank word.", nwAlert.ERROR) + self.theParent.makeAlert(self.tr("Cannot add a blank word."), nwAlert.ERROR) return False if self.listBox.findItems(newWord, Qt.MatchExactly): self.theParent.makeAlert( - "The word '%s' is already in the word list." % newWord, nwAlert.ERROR + self.tr("The word '{0}' is already in the word list.").format(newWord), + nwAlert.ERROR ) return False diff --git a/nw/gui/writingstats.py b/nw/gui/writingstats.py index dec1fef4..44fd38e5 100644 --- a/nw/gui/writingstats.py +++ b/nw/gui/writingstats.py @@ -72,7 +72,7 @@ class GuiWritingStats(QDialog): self.timeFilter = 0.0 self.wordOffset = 0 - self.setWindowTitle("Writing Statistics") + self.setWindowTitle(self.tr("Writing Statistics")) self.setMinimumWidth(self.mainConf.pxInt(420)) self.setMinimumHeight(self.mainConf.pxInt(400)) self.resize( @@ -95,7 +95,13 @@ class GuiWritingStats(QDialog): ) self.listBox = QTreeWidget() - self.listBox.setHeaderLabels(["Session Start", "Length", "Idle", "Words", "Histogram"]) + self.listBox.setHeaderLabels([ + self.tr("Session Start"), + self.tr("Length"), + self.tr("Idle"), + self.tr("Words"), + self.tr("Histogram"), + ]) self.listBox.setIndentation(0) self.listBox.setColumnWidth(self.C_TIME, wCol0) self.listBox.setColumnWidth(self.C_LENGTH, wCol1) @@ -125,7 +131,7 @@ class GuiWritingStats(QDialog): self.barImage.fill(self.palette().highlight().color()) # Session Info - self.infoBox = QGroupBox("Sum Totals", self) + self.infoBox = QGroupBox(self.tr("Sum Totals"), self) self.infoForm = QGridLayout(self) self.infoBox.setLayout(self.infoForm) @@ -153,12 +159,12 @@ class GuiWritingStats(QDialog): self.totalWords.setFont(self.theTheme.guiFontFixed) self.totalWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight) - self.infoForm.addWidget(QLabel("Total Time:"), 0, 0) - self.infoForm.addWidget(QLabel("Idle Time:"), 1, 0) - self.infoForm.addWidget(QLabel("Filtered Time:"), 2, 0) - self.infoForm.addWidget(QLabel("Novel Word Count:"), 3, 0) - self.infoForm.addWidget(QLabel("Notes Word Count:"), 4, 0) - self.infoForm.addWidget(QLabel("Total Word Count:"), 5, 0) + self.infoForm.addWidget(QLabel(self.tr("{0}:").format(self.tr("Total Time"))), 0, 0) + self.infoForm.addWidget(QLabel(self.tr("{0}:").format(self.tr("Idle Time"))), 1, 0) + self.infoForm.addWidget(QLabel(self.tr("{0}:").format(self.tr("Filtered Time"))), 2, 0) + self.infoForm.addWidget(QLabel(self.tr("{0}:").format(self.tr("Novel Word Count"))), 3, 0) + self.infoForm.addWidget(QLabel(self.tr("{0}:").format(self.tr("Notes Word Count"))), 4, 0) + self.infoForm.addWidget(QLabel(self.tr("{0}:").format(self.tr("Total Word Count"))), 5, 0) self.infoForm.addWidget(self.labelTotal, 0, 1) self.infoForm.addWidget(self.labelIdleT, 1, 1) self.infoForm.addWidget(self.labelFilter, 2, 1) @@ -170,7 +176,7 @@ class GuiWritingStats(QDialog): # Filter Options sPx = self.theTheme.baseIconSize - self.filterBox = QGroupBox("Filters", self) + self.filterBox = QGroupBox(self.tr("Filters"), self) self.filterForm = QGridLayout(self) self.filterBox.setLayout(self.filterForm) @@ -210,12 +216,12 @@ class GuiWritingStats(QDialog): ) self.showIdleTime.clicked.connect(self._updateListBox) - self.filterForm.addWidget(QLabel("Count novel files"), 0, 0) - self.filterForm.addWidget(QLabel("Count note files"), 1, 0) - self.filterForm.addWidget(QLabel("Hide zero word count"), 2, 0) - self.filterForm.addWidget(QLabel("Hide negative word count"), 3, 0) - self.filterForm.addWidget(QLabel("Group entries by day"), 4, 0) - self.filterForm.addWidget(QLabel("Show idle time"), 5, 0) + self.filterForm.addWidget(QLabel(self.tr("Count novel files")), 0, 0) + self.filterForm.addWidget(QLabel(self.tr("Count note files")), 1, 0) + self.filterForm.addWidget(QLabel(self.tr("Hide zero word count")), 2, 0) + self.filterForm.addWidget(QLabel(self.tr("Hide negative word count")), 3, 0) + self.filterForm.addWidget(QLabel(self.tr("Group entries by day")), 4, 0) + self.filterForm.addWidget(QLabel(self.tr("Show idle time")), 5, 0) self.filterForm.addWidget(self.incNovel, 0, 1) self.filterForm.addWidget(self.incNotes, 1, 1) self.filterForm.addWidget(self.hideZeros, 2, 1) @@ -236,7 +242,7 @@ class GuiWritingStats(QDialog): self.optsBox = QHBoxLayout() self.optsBox.addStretch(1) - self.optsBox.addWidget(QLabel("Word count cap for the histogram"), 0) + self.optsBox.addWidget(QLabel(self.tr("Word count cap for the histogram")), 0) self.optsBox.addWidget(self.histMax, 0) # Buttons @@ -244,19 +250,22 @@ class GuiWritingStats(QDialog): self.buttonBox.rejected.connect(self._doClose) self.btnClose = self.buttonBox.addButton(QDialogButtonBox.Close) + self.buttonBox.button(QDialogButtonBox.Close).setText(self.tr("Close")) self.btnClose.setAutoDefault(False) - self.btnSave = self.buttonBox.addButton("Save As", QDialogButtonBox.ActionRole) + self.btnSave = self.buttonBox.addButton(self.tr("Save As"), QDialogButtonBox.ActionRole) self.btnSave.setAutoDefault(False) self.saveMenu = QMenu(self) self.btnSave.setMenu(self.saveMenu) - self.saveJSON = QAction("JSON Data File (.json)", self) + self.saveJSON = QAction(self.tr("{0} ({1})").format( + self.tr("JSON Data File"), ".json"), self) self.saveJSON.triggered.connect(lambda: self._saveData(self.FMT_JSON)) self.saveMenu.addAction(self.saveJSON) - self.saveCSV = QAction("CSV Data File (.csv)", self) + self.saveCSV = QAction(self.tr("{0} ({1})").format( + self.tr("CSV Data File"), ".csv"), self) self.saveCSV.triggered.connect(lambda: self._saveData(self.FMT_CSV)) self.saveMenu.addAction(self.saveCSV) @@ -338,10 +347,10 @@ class GuiWritingStats(QDialog): if dataFmt == self.FMT_JSON: fileExt = "json" - textFmt = "JSON Data File" + textFmt = self.tr("JSON Data File") elif dataFmt == self.FMT_CSV: fileExt = "csv" - textFmt = "CSV Data File" + textFmt = self.tr("CSV Data File") else: return False @@ -356,7 +365,7 @@ class GuiWritingStats(QDialog): dlgOpt = QFileDialog.Options() dlgOpt |= QFileDialog.DontUseNativeDialog savePath, _ = QFileDialog.getSaveFileName( - self, "Save Document As", savePath, options=dlgOpt + self, self.tr("Save Document As"), savePath, options=dlgOpt ) if not savePath: return False @@ -474,7 +483,7 @@ class GuiWritingStats(QDialog): except Exception as e: self.theParent.makeAlert( - ["Failed to read session log file.", str(e)], nwAlert.ERROR + [self.tr("Failed to read session log file."), str(e)], nwAlert.ERROR ) return False diff --git a/nw/guimain.py b/nw/guimain.py index 6cb68cb3..50123955 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -122,8 +122,8 @@ class GuiMain(QMainWindow): self.projTabs = QTabWidget() self.projTabs.setTabPosition(QTabWidget.South) self.projTabs.setStyleSheet(r"QTabWidget::pane {border: 0;};") - self.projTabs.addTab(self.treeView, "Project") - self.projTabs.addTab(self.novelView, "Novel") + self.projTabs.addTab(self.treeView, self.tr("Project")) + self.projTabs.addTab(self.novelView, self.tr("Novel")) self.projTabs.currentChanged.connect(self._projTabsChanged) tabFont = self.projTabs.tabBar().font() @@ -139,17 +139,17 @@ class GuiMain(QMainWindow): self.treeButtons.setStyleSheet(r"QToolBar {padding: 0;}") self.projTabs.setCornerWidget(self.treeButtons, Qt.BottomRightCorner) - self.projDetailsBtn = QAction("Project Details") + self.projDetailsBtn = QAction(self.tr("Project Details")) self.projDetailsBtn.setIcon(self.theTheme.getIcon("status_lines")) self.projDetailsBtn.triggered.connect(lambda: self.showProjectDetailsDialog()) self.treeButtons.addAction(self.projDetailsBtn) - self.projStatsBtn = QAction("Writing Statistics") + self.projStatsBtn = QAction(self.tr("Writing Statistics")) self.projStatsBtn.setIcon(self.theTheme.getIcon("status_stats")) self.projStatsBtn.triggered.connect(lambda: self.showWritingStatsDialog()) self.treeButtons.addAction(self.projStatsBtn) - self.projSettingsBtn = QAction("Project Settings") + self.projSettingsBtn = QAction(self.tr("Project Settings")) self.projSettingsBtn.setIcon(self.theTheme.getIcon("settings")) self.projSettingsBtn.triggered.connect(lambda: self.showProjectSettingsDialog()) self.treeButtons.addAction(self.projSettingsBtn) @@ -180,12 +180,12 @@ class GuiMain(QMainWindow): self.splitOutline.addWidget(self.projMeta) self.splitOutline.setSizes(self.mainConf.getOutlinePanePos()) - # Main Tabs : Editor / Outline + # Main Tabs : Edirot / Outline self.mainTabs = QTabWidget() self.mainTabs.setTabPosition(QTabWidget.East) self.mainTabs.setStyleSheet(r"QTabWidget::pane {border: 0;}") - self.mainTabs.addTab(self.splitDocs, "Editor") - self.mainTabs.addTab(self.splitOutline, "Outline") + self.mainTabs.addTab(self.splitDocs, self.tr("Editor")) + self.mainTabs.addTab(self.splitOutline, self.tr("Outline")) self.mainTabs.currentChanged.connect(self._mainTabChanged) # Splitter : Project Tree / Main Tabs @@ -339,7 +339,7 @@ class GuiMain(QMainWindow): if self.hasProject: if not self.closeProject(): self.makeAlert( - "Cannot create new project when another project is open.", + self.tr("Cannot create new project when another project is open."), nwAlert.ERROR ) return False @@ -357,7 +357,8 @@ class GuiMain(QMainWindow): if os.path.isfile(os.path.join(projPath, self.theProject.projFile)): self.makeAlert( - "A project already exists in that location. Please choose another folder.", + self.tr("A project already exists in that location. " + "Please choose another folder."), nwAlert.ERROR ) return False @@ -372,7 +373,7 @@ class GuiMain(QMainWindow): self.statusBar.setRefTime(self.theProject.projOpened) self.statusBar.setProjectStatus(True) self.statusBar.setDocumentStatus(None) - self.statusBar.setStatus("New project created ...") + self.statusBar.setStatus(self.tr("New project created ...")) self._updateWindowTitle(self.theProject.projName) else: self.theProject.clearProject() @@ -391,8 +392,9 @@ class GuiMain(QMainWindow): if not isYes: msgYes = self.askQuestion( - "Close Project", - "Close the current project?
Changes are saved automatically." + self.tr("Close Project"), + "%s
%s" % (self.tr("Close the current project?"), + self.tr("Changes are saved automatically.")) ) if not msgYes: return False @@ -407,7 +409,8 @@ class GuiMain(QMainWindow): doBackup = True if self.mainConf.askBeforeBackup: msgYes = self.askQuestion( - "Backup Project", "Backup the current project?" + self.tr("Backup Project"), + self.tr("Backup the current project?") ) if not msgYes: doBackup = False @@ -457,13 +460,14 @@ class GuiMain(QMainWindow): try: lockDetails = ( - "

The project was locked by the computer " - "'%s' (%s %s), last active on %s" - ) % ( - self.theProject.lockedBy[0], - self.theProject.lockedBy[1], - self.theProject.lockedBy[2], - datetime.fromtimestamp( + "
%s" % self.tr("The project was locked by the computer " + "'{computer_name}' ({os_name} {os_version}), " + "last active on {time}") + ).format( + computer_name = self.theProject.lockedBy[0], + os_name = self.theProject.lockedBy[1], + os_version = self.theProject.lockedBy[2], + time = datetime.fromtimestamp( int(self.theProject.lockedBy[3]) ).strftime("%x %X") ) @@ -472,14 +476,16 @@ class GuiMain(QMainWindow): msgBox = QMessageBox() msgRes = msgBox.warning( - self, "Project Locked", ( - "The project is already open by another instance of novelWriter, and " - "is therefore locked. Override lock and continue anyway?

" - "Note: If the program or the computer previously crashed, the lock " - "can safely be overridden. If, however, another instance of " - "novelWriter has the project open, overriding the lock may corrupt " - "the project, and is not recommended.%s" - ) % lockDetails, + self, self.tr("Project Locked"), + "%s

%s
%s" % ( + self.tr("The project is already open by another instance of novelWriter, and " + "is therefore locked. Override lock and continue anyway?"), + self.tr("Note: If the program or the computer previously crashed, the lock " + "can safely be overridden. If, however, another instance of " + "novelWriter has the project open, overriding the lock may corrupt " + "the project, and is not recommended."), + lockDetails + ), QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) if msgRes == QMessageBox.Yes: @@ -685,15 +691,16 @@ class GuiMain(QMainWindow): lastPath = self.mainConf.lastPath extFilter = [ - "Text files (*.txt)", - "Markdown files (*.md)", - "novelWriter files (*.nwd)", - "All files (*.*)", + self.tr("{0} ({1})").format(self.tr("Text files"), "*.txt"), + self.tr("{0} ({1})").format(self.tr("Markdown files")), + self.tr("{0} ({1})").format(self.tr("novelWriter files"), "*.nwd"), + self.tr("{0} ({1})").format(self.tr("All files")), ] dlgOpt = QFileDialog.Options() dlgOpt |= QFileDialog.DontUseNativeDialog loadFile, _ = QFileDialog.getOpenFileName( - self, "Import File", lastPath, options=dlgOpt, filter=";;".join(extFilter) + self, self.tr("Import File"), lastPath, + options=dlgOpt, filter=";;".join(extFilter) ) if not loadFile: return False @@ -708,22 +715,25 @@ class GuiMain(QMainWindow): self.mainConf.setLastPath(loadFile) except Exception as e: self.makeAlert( - ["Could not read file. The file must be an existing text file.", str(e)], + [ + self.tr("Could not read file. The file must be an existing text file."), + str(e) + ], nwAlert.ERROR ) return False if self.docEditor.theHandle is None: self.makeAlert( - "Please open a document to import the text file into.", + self.tr("Please open a document to import the text file into."), nwAlert.ERROR ) return False if not self.docEditor.isEmpty(): - msgYes = self.askQuestion("Import Document", ( - "Importing the file will overwrite the current content of the document. " - "Do you want to proceed?" + msgYes = self.askQuestion(self.tr("Import Document"), ( + self.tr("Importing the file will overwrite the current content of the document. " + "Do you want to proceed?") )) if not msgYes: return False @@ -861,9 +871,11 @@ class GuiMain(QMainWindow): for nDone, tItem in enumerate(self.theProject.projTree): if tItem is not None: - self.setStatus("Indexing: '%s'" % tItem.itemName) + self.setStatus(self.tr("{0}: '{1}'").format(self.tr("Indexing"), tItem.itemName)) else: - self.setStatus("Indexing: Unknown item") + self.setStatus(self.tr("{0}: {1}").format( + self.tr("Indexing"), + self.tr("Unknown item"))) if tItem is not None and tItem.itemType == nwItemType.FILE: logger.verbose("Scanning: %s" % tItem.itemName) @@ -881,12 +893,14 @@ class GuiMain(QMainWindow): self.treeView.projectWordCount() tEnd = time() - self.setStatus("Indexing completed in %.1f ms" % ((tEnd - tStart)*1000.0)) + self.setStatus(self.tr("Indexing completed in {0} ms"). + format(f"{(tEnd - tStart)*1000.0:.1f}")) self.docEditor.updateTagHighLighting() qApp.restoreOverrideCursor() if not beQuiet: - self.makeAlert("The project index has been successfully rebuilt.", nwAlert.INFO) + self.makeAlert(self.tr("The project index has been successfully rebuilt."), + nwAlert.INFO) return True @@ -914,7 +928,7 @@ class GuiMain(QMainWindow): dlgOpt |= QFileDialog.ShowDirsOnly dlgOpt |= QFileDialog.DontUseNativeDialog projPath = QFileDialog.getExistingDirectory( - self, "Save novelWriter Project", "", options=dlgOpt + self, self.tr("Save novelWriter Project"), "", options=dlgOpt ) if projPath: return projPath @@ -1100,14 +1114,14 @@ class GuiMain(QMainWindow): # Popup msgBox = QMessageBox() if theLevel == nwAlert.INFO: - msgBox.information(self, "Information", popMsg) + msgBox.information(self, self.tr("Information"), popMsg) elif theLevel == nwAlert.WARN: - msgBox.warning(self, "Warning", popMsg) + msgBox.warning(self, self.tr("Warning"), popMsg) elif theLevel == nwAlert.ERROR: - msgBox.critical(self, "Error", popMsg) + msgBox.critical(self, self.tr("Error"), popMsg) elif theLevel == nwAlert.BUG: - popMsg += "
This is a bug!" - msgBox.critical(self, "Internal Error", popMsg) + popMsg += "
%s" % self.tr("This is a bug!") + msgBox.critical(self, self.tr("Internal Error"), popMsg) return @@ -1115,7 +1129,8 @@ class GuiMain(QMainWindow): """Ask the user a Yes/No question. """ msgBox = QMessageBox() - msgRes = msgBox.question(self, theTitle, theQuestion) + print(msgBox.standardButtons()) + msgRes = msgBox.question(self, theTitle, theQuestion, QMessageBox.Yes | QMessageBox.No) return msgRes == QMessageBox.Yes def reportConfErr(self): @@ -1137,8 +1152,9 @@ class GuiMain(QMainWindow): """ if self.hasProject: msgYes = self.askQuestion( - "Exit", - "Do you want to exit novelWriter?
Changes are saved automatically." + self.tr("Exit"), + "%s
%s" % (self.tr("Do you want to exit novelWriter?"), + self.tr("Changes are saved automatically.")) ) if not msgYes: return False diff --git a/nw/languages/nw_pt.qm b/nw/languages/nw_pt.qm new file mode 100644 index 0000000000000000000000000000000000000000..a6d88a4967836e32a7c948c4d2aeec9d0461db59 GIT binary patch literal 145009 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%0Dh7- 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 literal 0 HcmV?d00001 diff --git a/nw/languages/nw_pt.ts b/nw/languages/nw_pt.ts new file mode 100644 index 00000000..90159e08 --- /dev/null +++ b/nw/languages/nw_pt.ts @@ -0,0 +1,7067 @@ + + + + + Constant + + + Tag + Etiqueta + + + + Point of View + Ponto de Vista + + + + Characters + Personagens + + + + Plot + Enredo + + + + Timeline + Linha do Tempo + + + + Locations + Lugares + + + + Objects + Objetos + + + + Entities + Entidades + + + + Custom + Outros + + + + None + Nenhum + + + + Novel + Livro + + + + Entity + Entidade + + + + Outtakes + Removidos + + + + Trash + Lixeira + + + + Title Page + Página de Título + + + + Book + Livro + + + + Plain Page + Página Comum + + + + Partition + Partição + + + + Unnumbered + Sem Numeração + + + + Chapter + Capítulo + + + + Scene + Cena + + + + Note + Nota + + + + Title + Título + + + + Level + Nível + + + + Document + Documento + + + + Line + Linha + + + + Chars + Caracteres + + + + Words + Palavras + + + + Pars + Par. + + + + POV + P.V. + + + + Synopsis + Sinopse + + + + Straight single quotation mark + Aspas simples retas + + + + Straight double quotation mark + Aspas duplas retas + + + + Left single quotation mark + Aspas simples à esquerda + + + + Right single quotation mark + Aspas simples à direita + + + + Single low-9 quotation mark + Aspas 9-baixo simples + + + + Single high-reversed-9 quotation mark + Aspas 9-alto-invertido simples + + + + Left double quotation mark + Aspas duplas à esquerda + + + + Right double quotation mark + Aspas duplas à direita + + + + Double low-9 quotation mark + Aspas 9-baixo duplas + + + + Double high-reversed-9 quotation mark + Aspas 9-alto-invertido duplas + + + + Double low-reversed-9 quotation mark + Aspas 9-baixo-invertido duplas + + + + Single left-pointing angle quotation mark + Aspas angulares simples à esquerda + + + + Single right-pointing angle quotation mark + Aspas angulares simples à direita + + + + Left-pointing double angle quotation mark + Aspas angulares duplas à esquerda + + + + Right-pointing double angle quotation mark + Aspas angulares duplas à direita + + + + Left corner bracket + Colchete de canto à esquerda + + + + Right corner bracket + Colchete de canto à direita + + + + Left white corner bracket + Colchete branco de canto à esquerda + + + + Right white corner bracket + Colchete branco de canto à direita + + + + Focus + Foco + + + + internal + interno + + + + enchant + enchant + + + + GuiAbout + + + About novelWriter + Sobre o novelWriter + + + + novelWriter is a markdown-like text editor designed for organising and writing novels. It is written in Python 3 with a Qt5 GUI, using PyQt5. + novelWriter é um editor de texto som sintaxe semelhante ao markdown, projetado para a escrita e organização de livros. É escrito em Python 3 com interface de usuário em Qr5, usando PyQt5. + + + + novelWriter 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. + novelWriter é distribuído na especativa de que seja útil, mas SEM NENHUMA GARANTIA, nem mesmo a garantia implícita de COMERCIALIZAÇÃO ou ADEQUAÇÃO PARA UM PROPÓSITO ESPECÍFICO. + + + + Credits + Créditos + + + + novelWriter 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 3 of the License, or (at your option) any later version. + novelWriter é um software livre: você pode redistribuí-lo e/ou modificá-lo sob os termos da GNU Licença Pública Geral, assim como publicada pela Free Software Foundation, tanto na versão 3 da licença, ou (à sua escolha) qualquer versão subsequente. + + + + Author + Autor + + + + Credit + Créditos + + + + License + Licença + + + + Theme + Tema + + + + Icons + Ícones + + + + Syntax + Sintaxe + + + + Website + Website + + + + novelWriter + novelWriter + + + + About + Sobre + + + + Release + Lançamento + + + + {0}: {1} + + + + + See the License tab for the full license text, or visit the GNU website at {0} for more details. + Veja a aba de Licença para o texto completo de licença, ou visite o website da GNU em {0} para mais detalhes. + + + + <b>{0}:</b> {1} + + + + + <b>{0{0}</b> {{1} + Nâo traduzir + + + + OK + OK + + + + GuiBuildNovel + + + Failed to generate preview. The result is too big. + A geração do rascunho falhou. O resultado é muito grande. + + + + Open Document + Abrir Documento + + + + PDF + PDF + + + + Plain HTML + HTML Simples + + + + Unknown format + Formato desconhecido + + + + novelWriter HTML + HTML do novelWriter + + + + novelWriter Markdown + Markdown do novelWriter + + + + JSON + novelWriter HTML + JSON + HTML do novelWriter + + + + JSON + novelWriters Markdown + JSON + Markdown do novelWriter + + + + Build Novel Project + Construção do Projeto do Livro + + + + Title Formats for Novel Files + Formatos de Título para Arquivos do Livro + + + + Title + Título + + + + Chapter + Capítulo + + + + Unnumbered + Sem Numeração + + + + Section + Seção + + + + Font family + Família da fonte + + + + Font size + Tamanho da fonte + + + + Justify text + Texto justificado + + + + Disable styling + Desabilita a estilização + + + + Include synopsis + Inclui a sinopse + + + + Include comments + Inclui comentários + + + + Include keywords + Inclui palavras-chave + + + + Include body text + Inclui o corpo do texto + + + + File Filter Options + Opções de Filtro de Arquivos + + + + Include files with layouts 'Book', 'Page', 'Partition', 'Chapter', 'Unnumbered', and 'Scene'. + Inclui arquivos com os layouts 'Livro', 'Página', 'Partição', 'Sem-Numeração' e 'Cena'. + + + + Include files with layout 'Note'. + Inclui arquivos com o layout 'Nota'. + + + + Ignore the 'Include when building project' setting and include all files in the output. + Ignora a configuração 'Inclui quando estiver construindo o projeto' e inclui todos os arquivos no resultado. + + + + Include novel files + Inclui arquivos do livro + + + + Include note files + Inclui arquivos de notas + + + + Ignore export flag + Ignora opção de exportação + + + + Export Options + Opções de Exportação + + + + Replace tabs with spaces + Substitui tabulações com espaços + + + + Print + Imprimir + + + + Close + Fechar + + + + Save Document As + Salvar Documento Como + + + + {0} ({1}) + Não traduzir + + + + {0} file successfully written to: + Arquivo {0} escrito com sucesso para: + + + + Failed to write {0} file. {1} + Falhou para escrever o arquivo {0}. {1} + + + + Build Preview + Construir Prévia + + + + Print Preview + Imprimir Prévia + + + + Print to PDF + Imprimir para PDF + + + + Flat Open Document + + + + + Standard Markdown + Markdown Padrão + + + + GitHub Markdown + Markdown do GitHub + + + + There were problems when building the project + Houveram problemas ao construir o projeto + + + + JSON + novelWriter Markdown + JSON + Markdown do novelWriter + + + + Scene + Cena + + + + Font Options + Opções de Fonte + + + + Line height + Altura da linha + + + + Include Options + Opções de Inclusão + + + + Replace Unicode in HTML + Substituir Unicode no HTML + + + + {0}: + + + + + {0} for the title as set in the document + {0} para o titulo como definido no documento + + + + {0} for chapter number (1, 2, 3) + {0} para o numero do capítulo (1, 2, 3) + + + + {0} for chapter number as a word (one, two) + {0} para o numero do capítulo por extenso (um, dois) + + + + {0} for chapter number in upper case Roman + {0} para o número do capítulo em numerais romanos maiúsculos + + + + {0} for chapter number in lower case Roman + {0} para o número do capítulo em numerais romanos minúsculos + + + + {0} for scene number within chapter + {0} para o número da cena no capítulo + + + + {0} for scene number within novel + {0} para o número da cena no livro + + + + Leave blank to skip this heading, or set to a static text, like for instance '{0}', to make a separator. The separator will be centred automatically and only appear between sections of the same type. + Deixe em branco para ignorar este cabeçalho ou defina um texto estático como {0}, por exemplo, para fazer um separador. O separador será centralizado automaticamente a aparecerá apenas entre seções do mesmo tipo. + + + + GuiBuildNovelDocView + + + Build Time + Tempo de Construção + + + + Unknown + Desconhecido + + + + <b>{0}:</b> {1} + + + + + This area will show the content of the document to be exported or printed. Press the "Build Preview" button to generate content. + Esta área vai mostrar o conteúdo do documento a ser exportado ou impresso. Clique no botão "Construir Prévia" para gerar o conteúdo. + + + + GuiDocEditFooter + + + Line + Linha + + + + Words + Palavras + + + + Status + Estado + + + + {0}: {1} + + + + + {0}: {1} ({2}u202f%%) + + + + + {0}: {1} ({2}) + + + + + Document size is {0} bytes + O tamanho do documento é {0} bytes + + + + GuiDocEditHeader + + + Edit document meta + Editar os meta-dados do documento + + + + Search document + Procurar no documento + + + + Toggle Focus Mode + Alternar o "Modo Foco" + + + + Close the document + Fechar o documento + + + + GuiDocEditSearch + + + Search + Pesquisa + + + + Replace + Substituir + + + + Case Sensitive + Diferenciar Maiúsculas e Minúsculas + + + + Match case + Diferencia Maiúsculas e Minúsculas + + + + Whole Words Only + Apenas Palavras Inteiras + + + + Match whole words + Encontra apenas palavras inteiras + + + + RegEx Mode + Expressão Regular + + + + Loop Search + Pesquisa do Início + + + + Loop the search when reaching the end + Pesquisa do início quando chega no final do documento + + + + Search Next File + Busca no Próximo Arquivo + + + + Continue searching in the next file + Continua a busca no próximo arquivo + + + + Preserve Case + Preserva Maiúsculas e Minúsculas + + + + Preserve case on replace + Preserva maiúsculas e minúsculas ao substituir + + + + Close Search + Fechar a Busca + + + + Show/hide the replace text box + Mostrar/Ocultar a caixa substituição + + + + Find in current document + Encontrar no documento atual + + + + Find and replace in current document + Encontrar e substituir no documento atual + + + + Use regular expressions (requires Qt {0}) + Usar expressões regulares (necessita do Qt {0}) + + + + Close the search box [{0}] + Fechar a caixa de busca [{0}] + + + + GuiDocEditor + + + Spell check complete + Verificação ortográfica completa + + + + File details for the currently open file + Detalhes do arquivo aberto atualmente + + + + Handle + Referência + + + + Location + Local + + + + No Suggestions + Sem Sugestões + + + + The document you are trying to open is too big. The document size is {doc_size}. The maximum size allowed is {max_size}. + O documento que você está tentando abrir é muito grande. O tamanho do documento é {doc_size}. O tamanho máximo permitido é {max_size}. + + + + The text you are trying to add is too big. The text size is {text_size}. The maximum size allowed is {max_size}. + O texto que você está tentando adicionar é muito grande. O tamanho do texto é {text_size}. O tamanho máximo permitido é {max_size}. + + + + File Location + Localização do Arquivo + + + + The document has grown too big and you cannot add more text to it. The maximum size of a single novelWriter document is {max_size}. + O tamanho do documento aumentou muito e você não pode adicionar mais texto nele. O tamanho máximo de um único documento do novelWriter é {max_size}. + + + + Follow Tag + Seguir Etiqueta + + + + Cut + Recortar + + + + Copy + Copiar + + + + Paste + Colar + + + + Select All + Selecionar Tudo + + + + Select Word + Selecionar Palavra + + + + Select Paragraph + Selecionar Parágrafo + + + + Spelling Suggestion(s) + Sugestão de Ortografia + + + + Add Word to Dictionary + Adicionar Palavra ao Dicionário + + + + Please selection some text before calling replace quotes. + Por favor, selecione algum texto antes de invocar a substituição de aspas. + + + + {0}u202fMB + + + + + {0}<br> + + + + + {0}: {1} + + + + + {0} [{1}] + + + + + GuiDocMerge + + + Documents to Merge + Documentos para Mesclar + + + + Drag and drop items to change the order. + Arraste e solte items para mudar a ordem. + + + + Merge Documents + Mescla de Documentos + + + + No source documents found. Nothing to do. + Nenhum documento-fonte foi encontrado. Nada para fazer. + + + + No source document selected. Nothing to do. + Nenhum documento de origem selecionado. Nada a ser feito. + + + + Could not parse source document. + Não foi possível interpretar o documento. + + + + Element selected in the project tree must be a folder. + O elemento selecionado na árvore do projeto deve ser um diretório. + + + + Ok + OK + + + + Cancel + Cancelar + + + + GuiDocSplit + + + Document Headers + Cabeçalhos do Documento + + + + Select the maximum level to split into files. + Selecione o nível máximo para dividir em arquivos. + + + + Split on Header Level 1 (Title) + Dividir nos cabeçalhos de nível 1 (Título) + + + + Split up to Header Level 2 (Chapter) + Dividir até os cabeçalhos de nível 2 (Capítulo) + + + + Split up to Header Level 3 (Scene) + Dividir até os cabeçalhos de nível 3 (Cena) + + + + Split up to Header Level 4 (Section) + Dividir até os cabeçalhos de nível 4 (Seção) + + + + Split Document + Divisão de Documento + + + + No source document selected. Nothing to do. + Nenhum documento de origem selecionado. Nada a ser feito. + + + + Could not parse source document. + Não foi possível interpretar o documento. + + + + No headers found. Nothing to do. + Nenhum cabeçalho foi encontrado. Nada para fazer. + + + + Cannot add new folder for the document split. Maximum folder depth has been reached. Please move the file to another level in the project tree. + Não é possível adicionar um novo diretório para a divisão do documento. A profundidade máxima dos diretórios foi alcançada. Por favor mova o arquivo para outro nível na árvore do projeto. + + + + Continue with the splitting process? + Continuar com o processo de divisão? + + + + Element selected in the project tree must be a file. + O elemento selecionado na árvore do projeto deve ser um arquivo. + + + + The document will be split into {0} file(s) in a new folder. The original document will remain intact. + O documento será dividio em {0} arquivo(s) em um novo diretório. O documento original será mantido intacto. + + + + Ok + OK + + + + Cancel + Cancelar + + + + GuiDocViewFooter + + + Show/hide the references panel + Mostrar/ocultar o painel de referências + + + + Activate to freeze the content of the references panel when changing document + Ative para manter o conteúdo do painel de referências quando trocar o documento + + + + Show comments + Mostrar comentários + + + + Show synopsis comments + Mostrar comentários de sinopse + + + + References + Referências + + + + Sticky + Aderente + + + + Comments + Comentários + + + + Synopsis + Sinopse + + + + GuiDocViewHeader + + + Go backward + Voltar + + + + Go forward + Avançar + + + + Reload the document + Recarregar o documento + + + + Close the document + Fechar o documento + + + + GuiDocViewer + + + An error occurred while generating the preview. + Um erro ocorreu enquanto gerava o rascunho. + + + + Copy + Copiar + + + + Select All + Selecionar Tudo + + + + Select Word + Selecionar Palavra + + + + Select Paragraph + Selecionar Parágrafo + + + + Could not find the reference for tag '{0}'. It either doesn't exist, or the index is out of date. The index can be updated from the Tools menu, or by pressing {1}. + Não foi possível encontrar a referência para a etiqueta '{0}'. Pode ser que ela não exista ou que o índice esteja desatualizado. O índice pode ser atualizado à partir do menu Ferramentas ou pressionando {1}. + + + + GuiIcons + + + Could not load theme config file. + Não foi posível carregar o arquivo de configuração de tema. + + + + GuiItemDetails + + + Label + Rótulo + + + + Status + Estado + + + + Class + Classe + + + + Layout + Leiaute + + + + Characters + Caracteres + + + + Words + Palavras + + + + Paragraphs + Parágrafos + + + + GuiItemEditor + + + Item Settings + Configurações do Item + + + + Include when building project + Incluir ao construir o projeto + + + + Label + Rótulo + + + + Status + Estado + + + + Layout + Leiaute + + + + Ok + OK + + + + Cancel + Cancelar + + + + GuiMain + + + New project created ... + Novo projeto criado... + + + + The project was locked by the computer '{computer_name}' ({os_name} {os_version}), last active on {time} + O projeto foi bloqueado pelo computador '{computer_name}' ({os_name} {os_version}), última atividade em {time} + + + + The project is already open by another instance of novelWriter, and is therefore locked. Override lock and continue anyway? + O projeto já está aberto em outra instância do novelWriter, e portanto foi bloqueado. Deseja sobrescrever o bloqueio e continuar mesmo assim? + + + + Note: If the program or the computer previously crashed, the lock can safely be overridden. If, however, another instance of novelWriter has the project open, overriding the lock may corrupt the project, and is not recommended. + Nota: Se o programa ou o computador sofreu uma falha anteriormente, o bloqueio pode ser sobrescrito com segurança. Se, no entanto, outra instância do novelWriter esteja com o projeto aberto, sobrescrever o bloqueio pode corromper o projeto e não é recomendado. + + + + Text files + Arquivos de texto + + + + novelWriter files + Arquivos do novelWriter + + + + Indexing + Indexando + + + + Unknown item + Item desconhecido + + + + Information + Informação + + + + Warning + Alerta + + + + Error + Erro + + + + This is a bug! + Isto é um bug! + + + + Internal Error + Erro Interno + + + + Editor + Editor + + + + Outline + Estrutura + + + + Cannot create new project when another project is open. + Não é possível criar um novo projeto quando outro projeto está aberto. + + + + A project already exists in that location. Please choose another folder. + Um projeto já existe neste local. Por favor escolha outro diretório. + + + + Close Project + Fechar Projeto + + + + Close the current project? + Fechar o projeto atual? + + + + Changes are saved automatically. + As alterações serão salvas automaticamente. + + + + Backup Project + Cria uma cópia de segurança do diretório do projeto + + + + Backup the current project? + Criar cópia de segurança do projeto atual? + + + + Project Locked + Projeto Bloqueado + + + + Markdown files + Arquivos Markdown + + + + All files + Todos os Arquivos + + + + Import File + Importar Arquivo + + + + Could not read file. The file must be an existing text file. + Não foi possível ler o arquivo. O arquivo deve ser um arquivo de texto existente. + + + + Please open a document to import the text file into. + Por favor, abra um documento para importar o text nele. + + + + Import Document + Importar Documento + + + + Importing the file will overwrite the current content of the document. Do you want to proceed? + Importar o arquivo vai sobrescrever o conteúdo atual do documento. Você deseja continuar? + + + + The project index has been successfully rebuilt. + O índice do projeto foi reconstruído com sucesso. + + + + Save novelWriter Project + Salvar o Projeto do novelWriter + + + + Exit + Sair + + + + Do you want to exit novelWriter? + Você deseja realmente sair do novelWriter? + + + + {0} ({1}) + + + + + {0}: '{1}' + + + + + {0}: {1} + + + + + Indexing completed in {0} ms + Indexação completa em {0} ms + + + + Project + Projeto + + + + Novel + Livro + + + + Project Details + Detalhes do Projeto + + + + Writing Statistics + Estatísticas de Escrita + + + + Project Settings + Configurações do Projeto + + + + GuiMainMenu + + + &Project + &Projeto + + + + New Project + Novo Projeto + + + + Create new project + Cria um projeto novo + + + + Open Project + Abrir Projeto + + + + Open project + Abre um projeto + + + + Save Project + Salvar Projeto + + + + Save project + Salva o projeto + + + + Close Project + Fechar Projeto + + + + Close project + Fecha o projeto + + + + Project Settings + Configurações do Projeto + + + + Project settings + Configurações do Projeto + + + + Create Root Folder + Criar Diretório de Projeto + + + + Novel Root + Livro + + + + Plot Root + Enredo + + + + Character Root + Personagem + + + + Location Root + Lugar + + + + Timeline Root + Linha do Tempo + + + + Object Root + Objeto + + + + Entity Root + Entidade + + + + Custom Root + Outro + + + + Outtakes Root + Removidos + + + + Create Folder + Criar Diretório + + + + Create folder + Cria um diretório + + + + Empty Trash + Esvaziar a Lixeira + + + + Permanently delete all files in the Trash folder + Remove permanentemente todos os arquivos da lixeira + + + + Exit + Sair + + + + Exit novelWriter + Sai do novelWriter + + + + &Document + &Documento + + + + New Document + Novo Documento + + + + Create new document + Cria um novo documento + + + + Open Document + Abrir Documento + + + + Open selected document + Abre o documento selecionado + + + + Save Document + Salvar Documento + + + + Save current document + Salva o documento atual + + + + Close Document + Fechar Documento + + + + Close current document + Fecha o documento atual + + + + View Document + Ver Documento + + + + View document as HTML + Visualiza o documento como HTML + + + + Close Document View + Fechar a Visualização do Documento + + + + Close document view pane + Fecha o painel de visualização do documento + + + + Show File Details + Mostrar Detalhes do Arquivo + + + + Shows a message box with the document location in the project folder + Mostra uma caixa de mensagens com a localização do documento no diretório do projeto + + + + Import from File + Importar de Arquivo + + + + Import document from a text or markdown file + Importa um documento de um arquivo de texto ou markdown + + + + Merge Folder to Document + Mesclar Diretório para um Documento + + + + Merge a folder of documents to a single document + Mescla um diretório para um único documento + + + + Split Document to Folder + Dividir Documento para Diretório + + + + Split a document into a folder of multiple documents + Divide um documento em um diretório com múltiplos documentos + + + + &Edit + &Editar + + + + Undo + Desfazer + + + + Undo last change + Desfaz a última alteração + + + + Redo + Refazer + + + + Redo last change + Refaz a última alteração + + + + Cut + Recortar + + + + Cut selected text + Recorta o texto selecionado + + + + Copy + Copiar + + + + Copy selected text + Copia o texto selecionado + + + + Paste + Colar + + + + Paste text from clipboard + Cola o texto da área de transferência + + + + Select All + Selecionar Tudo + + + + Select all text in document + Seleciona todo o texto do documento + + + + Select Paragraph + Selecionar Parágrafo + + + + Select all text in paragraph + Seleciona todo o texto do parágrafo + + + + &View + &Visualizar + + + + Focus Project Tree + Árvore do Projeto + + + + Move focus to project tree + Muda o foco para a árvore do projeto + + + + Focus Document Editor + Editor do Documento + + + + Move focus to left document pane + Muda o foco para o editor do documento + + + + Focus Document Viewer + Visualizador do Documento + + + + Move focus to right document pane + Muda o foco para o visualizador do documento + + + + Go Backward + Voltar + + + + Move backward in the view history of the right pane + Volta no histórico do editor do documento + + + + Go Forward + Avançar + + + + Move forward in the view history of the right pane + Avança no histórico do editor do documento + + + + Focus Mode + Modo de Foco + + + + Toggles a distraction free mode, only showing text editor + Alterna o modo livre de distrações, mostrando apenas o editor de texto + + + + Full Screen Mode + Tela Cheia + + + + Maximises the main window + Maximiza a tela principal + + + + &Insert + &Inserir + + + + Short Dash + Travessão Curto + + + + Long Dash + Travessão Longo + + + + Quote Marks + Aspas + + + + Left Single Quote + Aspas Simples à Esquerda + + + + Insert left single quote + Insere aspas simples à esquerda + + + + Right Single Quote + Aspas Simples à Direita + + + + Insert right single quote + Insere aspas simples à direita + + + + Left Double Quote + Aspas Duplas à Esquerda + + + + Insert left double quote + Insere aspas duplas à esquerda + + + + Right Double Quote + Aspas Duplas à Direita + + + + Insert right double quote + Insere aspas duplas à direita + + + + Alternative Apostrophe + Apóstrofo Alternativo + + + + Breaks and Spaces + Quebras e Espaços + + + + Hard Line Break + Quebra Forçada de Linha + + + + Insert a hard line break + Insere uma quebra forçada de linha + + + + Non-Breaking Space + Espaço Não-Separável + + + + Insert a non-breaking space + Insere um espaço não-separável + + + + Thin Space + Espaço Estreito + + + + Insert a thin space + Insere um espaço estreito + + + + Thin Non-Breaking Space + Espaço Estreito Não-Separável + + + + Insert a thin non-breaking space + Insere um espaço estreito não-separável + + + + Tags and References + Etiquetas e Referências + + + + &Search + Pe&squisa + + + + Find + Procurar + + + + Find text in document + Procura o texto no documento + + + + Replace + Substituir + + + + Replace text in document + Substitui o texto no documento + + + + Find Next + Procurar o Próximo + + + + Find next occurrence text in document + Procura a próxima ocorrência do texto no documento + + + + Find Previous + Procurar a Anterior + + + + Find previous occurrence text in document + Procura a ocorrência anterior no documento + + + + Replace Next + Substituir o Próximo + + + + Find and replace next occurrence text in document + Procura e substitui a próxima ocorrência do texto no documento + + + + &Format + &Formatar + + + + Emphasis + Ênfase + + + + Add emphasis to selected text (italic) + Adiciona ênfase ao texto selecionado (itálico) + + + + Strong Emphasis + Ênfase Forte + + + + Add strong emphasis to selected text (bold) + Adiciona ênfase forte ao texto selecionado (negrito) + + + + Strikethrough + Tachado + + + + Add strikethrough to selected text + Adiciona taxado ao texto selecionado + + + + Wrap Double Quotes + Aspas Duplas + + + + Wrap selected text in double quotes + Adiciona aspas dupla ao redor do texto selecionado + + + + Wrap Single Quotes + Aspas Simples + + + + Wrap selected text in single quotes + Adiciona aspas simples ao redor do texto selecionado + + + + Header 1 + Cabeçalho 1 + + + + Change the block format to Header 1 + Altera o formato do bloco para cabeçalho nível 1 + + + + Header 2 + Cabeçalho 2 + + + + Change the block format to Header 2 + Altera o formato do bloco para cabeçalho nível 2 + + + + Header 3 + Cabeçalho 3 + + + + Change the block format to Header 3 + Altera o formato do bloco para cabeçalho nível 3 + + + + Header 4 + Cabeçalho 4 + + + + Change the block format to Header 4 + Altera o formato do bloco para cabeçalho nível 4 + + + + Comment + Comentário + + + + Change the block format to comment + Altera o formato do bloco para comentário + + + + Remove Block Format + Limpar Formatação do Bloco + + + + Strips block format + Remove a formatação do bloco + + + + Replace Single Quotes + Substituir Aspas Simples + + + + Replace all straight single quotes in selected text + Substitui todas as aspas simples do texto selecionado + + + + Replace Double Quotes + Substituir Aspas Duplas + + + + Replace all straight double quotes in selected text + Substitui todas as aspas duplas do texto selecionado + + + + &Tools + Ferramen&tas + + + + Check Spelling + Checar Ortografia + + + + Toggle check spelling + Alterna a checagem de ortografia + + + + Re-Run Spell Check + Checar Ortografia Agora + + + + Run the spell checker on current document + Executa a checagem de ortografia no documento atual + + + + Rebuild Index + Reindexar + + + + Rebuild the tag indices and word counts + Reindexa as etiquetas e contagem de palavras + + + + Rebuild Outline + Recriar a Estrutura + + + + Rebuild the novel outline tree + Recria a estrutura do livro + + + + Auto-Update Outline + Recriar a Estrutura Automaticamente + + + + Update project outline when a novel file is changed + Recria a estrutura do projeto quando um arquivo é modificado + + + + Backup Project Folder + Criar Cópia de Segurança do Diretório do Projeto + + + + Backup Project + Cria uma cópia de segurança do diretório do projeto + + + + Build Novel Project + Construir o Projeto do Livro + + + + Launch the Build novel project tool + Inicia a ferramenta de construção do projeto + + + + Writing Statistics + Estatísticas de Escrita + + + + Show the writing statistics dialog + Mostra o diálogo de estatísticas de escrita + + + + Preferences + Preferências + + + + &Help + A&juda + + + + About novelWriter + Sobre o novelWriter + + + + About Qt5 + Sobre o Qt5 + + + + Documentation (Local) + Documentação (Local) + + + + View local documentation with Qt Assistant + Exibe a documentação local com o assistente do Qt + + + + Documentation (Online) + Documentação (Online) + + + + Report an Issue (GitHub) + Reportar um Problema (Github) + + + + Ask a Question (GitHub) + Fazer uma Pergunta (Github) + + + + Latest Release (GitHub) + Última Versão (Github) + + + + The novelWriter Website + Website do novelWriter + + + + View online documentation at {0} + Visualizar a documentação online em {0} + + + + Report a bug or issue on GitHub at {0} + Reportar um bug ou problema no Github em {0} + + + + Ask a question on GitHub at {0} + Perguntar uma dúvida no Github em {0} + + + + Open the Releases page on GitHub at {0} + Abrir a Página de Versões no Github em {0} + + + + Open the novelWriter website at {0} + Abrir o Website do novelWriter em {0} + + + + Edit Item + Editar Item + + + + Change project item settings + Modifica as configurações do item do projeto + + + + Delete Item + Remover Item + + + + Delete selected project item + Remove o item de projeto selecionado + + + + Dashes + Travessões + + + + Insert short dash (en dash) + Insere um travessão curto (meia risca) + + + + Insert long dash (em dash) + Inserir um travessão + + + + Horizontal Bar + Barra Horizontal + + + + Insert a horizontal bar (quotation dash) + Insere uma barra horizontal (travessão de fala) + + + + Figure Dash + Travessão de Número + + + + Insert figure dash (same width as a number character) + Insere um travessão de número (mesma largura de um caracter numérico) + + + + Insert modifier letter single apostrophe + Insere um caracter modificado de apóstrofo simples + + + + Project Details + Detalhes do Projeto + + + + Project details + Detalhes do projeto + + + + Move Item Up + Mover Item Acima + + + + Move project item up + Move o item de projeto para cima + + + + Move Item Down + Mover Item Abaixo + + + + Move project item down + Move o item de projeto para baixo + + + + Undo Last Move + Desfazer a Última Movimentação + + + + Undo last item move + Desfaz a última movimentação de item + + + + Focus Outline + Foco na Estrutura + + + + Move focus to outline + Move o foco para a estrutura + + + + General Punctuation + Pontuação Geral + + + + Ellipsis + Reticências + + + + Insert ellipsis + Insere reticências + + + + Prime + Prime + + + + Insert a prime symbol + Insere um simbolo prime + + + + Double Prime + Prime Duplo + + + + Insert a double prime symbol + Insere um simbolo prime duplo + + + + Other Symbols + Outros Simbolos + + + + List Bullet + Marcador de Lista + + + + Insert a list bullet + Insere um marcador de lista + + + + Hyphen Bullet + Marcador em Hífen + + + + Insert a hyphen bullet (alternative bullet) + Insere um marcador em hífen (marcador alternativo) + + + + Flower Mark + Marcador em Flor + + + + Insert a flower mark (alternative bullet) + Insere um marcador em flor (marcador alternativo) + + + + Per Mille + Por Milha + + + + Insert a per mille symbol + Insere um simbolo de "por milha" + + + + Degree Symbol + Simbolo de Grau + + + + Insert a degree symbol + Insere um simbolo de grau + + + + Minus Sign + Sinal de Menos + + + + Insert a minus sign (not a hypen or dash) + Insere um sinal de menos (não é um hífen nem travessão) + + + + Times Sign + Sinal de Multiplicação + + + + Insert a times sign (multiplication cross) + Insere um sinal de multiplicação (cruz de multiplicação) + + + + Division Sign + Sinal de Divisão + + + + Insert a division sign + Insere um sinal de divisão + + + + Project Word List + Lista de Palavras do Projeto + + + + Edit the project's word list + Editar a lista de palavras do projeto + + + + GuiMainStatus + + + None + Nenhum + + + + Provider + Provedor + + + + unknown + desconhecido + + + + Words + Palavras + + + + Editor + Editor + + + + Project + Projeto + + + + Session Time + Tempo de Sessão + + + + Project word count (session change) + Contagem de palavras do projeto (alteradas na sessão) + + + + {0}: {1} + + + + + GuiNovelTree + + + Title + Título + + + + Words + Palavras + + + + POV + P.V. + + + + Section title + Titulo da seção + + + + Word count + Contagem de palavras + + + + Point-of-view character + Personagem do ponto de vista + + + + GuiOutlineDetails + + + Chapter + Capítulo + + + + Scene + Cena + + + + Section + Seção + + + + Title + Título + + + + Document + Documento + + + + Status + Estado + + + + Characters + Caracteres + + + + Words + Palavras + + + + Paragraphs + Parágrafos + + + + Synopsis + Sinopse + + + + Title Details + Detalhes do Título + + + + Reference Tags + Referências das Etiquetas + + + + GuiOutlineHeaderMenu + + + Select Columns + Selecionar Colunas + + + + GuiPreferences + + + Some changes will not be applied until novelWriter has been restarted. + Algumas alterações não serão aplicadas enquanto a aplicação não for reiniciada. + + + + Preferences + Preferências + + + + General + Geral + + + + Projects + Projetos + + + + Editor + Editor + + + + Documents + Documentos + + + + Highlighting + Destaque + + + + Automation + Automação + + + + Ok + OK + + + + Cancel + Cancelar + + + + GuiPreferencesAutomation + + + Automatic Features + Funcionalidades Automáticas + + + + Auto-select word under cursor + Selecionar automaticamente a palavra sob o cursor + + + + Apply formatting to word under cursor if no selection is made. + Aplicar a formatação à palavra sob o cursor se nenhuma seleção for feita. + + + + Auto-replace text as you type + Substituir automaticamente o texto enquanto digita + + + + Allow the editor to replace symbols as you type. + Permite que o editor substituia símbolos emquanto você digita. + + + + Replace as You Type + Substituição Durante a Digitação + + + + Auto-replace single quotes + Substituir automaticamente aspas simples + + + + Try to guess which is an opening or a closing single quote. + Tenta adivinhar se a aspa simples é de abertura ou de fechamento. + + + + Auto-replace double quotes + Subsitituir automaticamente aspas duplas + + + + Try to guess which is an opening or a closing double quote. + Tenta adivinhar se a aspa dupla é de abertura ou de fechamento. + + + + Auto-replace dashes + Substituir automaticamente os travessões + + + + Double and triple hyphens become short and long dashes. + Hífens duplos ou triplos se tornam travessões curtos ou longos. + + + + Auto-replace dots + Substituir automaticamente os pontos + + + + Three consecutive dots become ellipsis. + Três pontos consecutivos se tornam uma reticência. + + + + Quotation Style + Estilo de Aspas + + + + Single quote open style + Estilo da aspa de abertura simples + + + + The symbol to use for a leading single quote. + O símbolo usado para a aspa simples à esquerda. + + + + Single quote close style + Estilo da aspa de fechamento simples + + + + The symbol to use for a trailing single quote. + O símbolo usado para a aspa simples à esquerda. + + + + Double quote open style + Estilo da aspa de abertura dupla + + + + The symbol to use for a leading double quote. + O símbolo usado para a aspa dupla à esquerda. + + + + Double quote close style + Estilo da aspa de fechamento dupla + + + + The symbol to use for a trailing double quote. + O símbolo usado para a aspa dupla à direita. + + + + GuiPreferencesDocuments + + + Text Style + Estilo do Texto + + + + Font family + Família da fonte + + + + Font for the document editor and viewer. + Fonte para o editor e visualizador de documentos. + + + + Font size + Tamanho da fonte + + + + Font size for the document editor and viewer. + Tamanho da fonte para o editor e visualizador de documentos. + + + + Text Flow + Fluxo do Texto + + + + Maximum text width in "Normal Mode" + Largura máxima do texto no "Modo Normal" + + + + Horizontal margins are scaled automatically. + Margens horizontais são redimensionadas automaticamente. + + + + Maximum text width in "Focus Mode" + Largura máxima do texto no "Modo Foco" + + + + Disable maximum text width in "Normal Mode" + Desabilita a largura máxima do texto no "Modo Normal" + + + + Text width is defined by the margins only. + A largura do texto é definida apenas pelas margens. + + + + Hide document footer in "Focus Mode" + Oculta o rodapé do documento no "Modo Foco" + + + + Hide the information bar at the bottom of the document. + Oculta a barra de informações na parte de baixo do documento. + + + + Justify the text margins in editor and viewer + Justifica as margens do texto no editor e visualizador + + + + Lay out text with straight edges in the editor and viewer. + Organiza o texto com cantos retos no editor e visualizador. + + + + Text margin + Margem do texto + + + + If maximum width is set, this becomes the minimum margin. + Se a largura máxima for definida, esta se torna a margem mínima. + + + + Tab width + Largura da tabulação + + + + The width of a tab key press in the editor and viewer. + A largura de uma tabulação no editor e visualizador. + + + + px + px + + + + GuiPreferencesEditor + + + Spell Checking + Correção Ortográfica + + + + {0} ({1}) + + + + + Internal + Interno + + + + Spell Enchant + Spell Enchant + + + + Spell check provider + Provedor de correção ortográfica + + + + Note that the internal spell check tool is quite slow. + Note que o corretor ortográfico interno é significativamente lento. + + + + Spell check language + Idioma do corretor ortográfico + + + + Available languages are determined by your system. + Os idiomas disponíveis são determinados pelo seu sistema. + + + + Big document limit + Limite de documento grande + + + + Full spell checking is disabled above this limit. + A verificação ortográfica é desabilitada acima desse limite. + + + + Writing Guides + Guias de Escrita + + + + Show tabs and spaces + Mostrar tabulações e espaços + + + + Add symbols to indicate tabs and spaces in the editor. + Adiciona símbolos para indicar tabulações e espaços no editor. + + + + Show line endings + Mostrar terminações de linha + + + + Add a symbol to indicate line endings in the editor. + Adiciona um símbolo para indicar a terminação de linha no editor. + + + + Scroll Behaviour + Comportamento da Rolagem + + + + Scroll past end of the document + Rolar após o final do documento + + + + Also improves trypewriter scrolling for short documents. + Também melhora a rolagem de máquina de escrever em documentos curtos. + + + + Typewriter style scrolling when you type + Rolagem no estilo de máquina de escrever quando digita + + + + Try to keep the cursor at a fixed vertical position. + Tenta manter o cursor em uma posição vertical fixa. + + + + Minimum position for Typewriter scrolling + Posição máxima da rolagem de máquina de escrever + + + + Percentage of the editor height from the top. + Porcentagem da altura do editor desde o topo. + + + + kB + kB + + + + Word Count + Contagem de Palavras + + + + Word count interval + Intervalo de contagem de palavras + + + + How often the word count is updated. + Com qual frequência a contagem de palavras é atualizada. + + + + seconds + segundos + + + + GuiPreferencesGeneral + + + Look and Feel + Aparência + + + + Main GUI theme + Tema da interface + + + + Changing this requires restarting novelWriter. + Alterações nessa configuração exigem reinício da aplicação. + + + + Main icon theme + Tema dos ícones + + + + Prefer icons for dark backgrounds + Preferir ícones para fundos escuros + + + + May improve the look of icons on dark themes. + Pode melhorar a aparência dos ícones em temas escuros. + + + + Font family + Família da fonte + + + + Font size + Tamanho da fonte + + + + GUI Settings + Configurações da Interface + + + + Show full path in document header + Mostrar o caminho completo do documento no cabeçalho + + + + Add the parent folder names to the header. + Adiciona o nome dos diretórios-pai ao cabeçalho. + + + + Hide vertical scroll bars in main windows + Ocultar a barra de rolagem vertical nas janelas principais + + + + Scrolling available with mouse wheel and keys only. + A rolagem de tela estará diponível apenas com o mouse ou teclado. + + + + Hide horizontal scroll bars in main windows + Ocultar a barra de rolagem horizontal nas janelas principais + + + + GuiPreferencesProjects + + + Automatic Save + Salvamento Automático + + + + Save document interval + Intervalo de salvamento do documento + + + + How often the open document is automatically saved. + Com qual frequência o documento aberto é salvo automaticamente. + + + + Save project interval + Intervalo de salvamento do projeto + + + + How often the open project is automatically saved. + Com qual frequencia o projeto aberto é salvo automaticamente. + + + + Project Backup + Cópia de Segurança + + + + Browse + Procurar + + + + Backup storage location + Localização da cópia de segurança + + + + {0}: {1} + + + + + Path + Caminho + + + + Run backup when the project is closed + Executar a cópia de segurança quando o projeto é fechado + + + + Can be overridden for individual projects in project settings. + Pode ser sobrescrito para projetos individuais nas configurações do projeto. + + + + Ask before running backup + Perguntar antes de executar a cópia de segurança + + + + If off, backups will run in the background. + Se desativado, cópias de segurança serão executadas em segundo plano. + + + + Backup Directory + Diretório das Cópias de Segurança + + + + seconds + segundos + + + + Session Timer + Temporizador da Sessão + + + + Pause the session timer when not writing + Pausa o temporizador da sessão quando não estiver escrevendo + + + + Also pauses when the application window does not have focus. + Também pausa quando a janela da aplicação não estiver em foco. + + + + Editor inactive time before pausing timer + Tempo inativo do editor antes de pausar o temporizador + + + + User activity includes typing and changing the content. + Atividades de usuário incluem escrever e alterar o conteúdo. + + + + minutes + minutos + + + + GuiPreferencesSyntax + + + Highlighting Theme + Tema do Destaque + + + + Highlighting theme + Tema do destaque + + + + Colour theme to apply to the editor and viewer. + Tema de cores para aplicar ao editor e visualizador. + + + + Quotes & Dialogue + Citações e Diálogos + + + + Highlight text wrapped in quotes + Destaca o texto em citações + + + + Applies to single, double and straight quotes. + Aplica-se a citações com aspas simples, duplas e retas. + + + + Allow open-ended single quotes + Permite citações com aspas simples sem fechamento + + + + Highlight single-quoted line with no closing quote. + Destaca a linha com citação de aspas simples sem aspas de fechamento. + + + + Allow open-ended double quotes + Permite citações com aspas duplas sem fechamento + + + + Highlight double-quoted line with no closing quote. + Destaca a linha com citação de aspas duplas sem aspas de fechamento. + + + + Text Emphasis + Ênfase de Texto + + + + Add highlight colour to emphasised text + Adiciona destaque de cor ao texto enfatizado + + + + Applies to emphasis (italic) and strong (bold). + Aplica-se à ênfase (itálico) e ênfase forte (negrito). + + + + GuiProjectDetails + + + Project Details + Detalhes do Projeto + + + + Overview + Visão Geral + + + + Contents + Conteúdo + + + + Close + Fechar + + + + GuiProjectDetailsContents + + + Title + Título + + + + Words + Palavras + + + + Pages + Páginas + + + + Page + Página + + + + Progress + Progresso + + + + Typical word count for a 5 by 8 inch book page with 11 pt font is 350. + Contagem de palavras típica para uma página de livro de 5 por 8 polegadas com a fonte de 11 pt é 350. + + + + Start counting page numbers from this page. + Inicia a contagem de numero de páginas à partir dessa página. + + + + Assume a new chapter or partition always start on an odd numbered page. + Assume que um capítulo ou partição sempre começa em uma página com numeração ímpar. + + + + Words per page + Palavras por página + + + + Count pages from + Contar páginas à partir da página + + + + Clear double pages + Eliminar páginas duplas + + + + Table of Contents + Sumário + + + + END + FIM + + + + GuiProjectDetailsMain + + + {0}: {1} + + + + + Working Title + Nome do projeto + + + + By {0} + Por {0} + + + + Words + Palavras + + + + Chapters + Capítulos + + + + Scenes + Cenas + + + + Revisions + Revisões + + + + Editing Time + Tempo de Edição + + + + Path + Caminho + + + + GuiProjectEditMain + + + Should be set only once. + Deve ser definido apenas uma vez. + + + + Change whenever you want! + Mude quando quiser! + + + + One name per line. + Um nome por linha. + + + + Default + Padrão + + + + Overrides main preferences. + Sobrescreve as preferências globais. + + + + Project Settings + Configurações do Projeto + + + + Working title + Nome do projeto + + + + Novel title + Título do tivro + + + + Author(s) + Autor(es) + + + + Spell check language + Idioma do corretor ortográfico + + + + No backup on close + Não salvar cópia de segurança ao fechar + + + + GuiProjectEditReplace + + + Keyword + Palavra-chave + + + + Replace With + Substituir Com + + + + Save entry + Salvar entrada + + + + Add new entry + Adiciona uma nova entrada + + + + Delete selected entry + Remove a entrada selecionada + + + + Text Replace List for Preview and Export + Lista de Substituição de Texto para o Preview ou Exportação + + + + <{0}> + + + + + GuiProjectEditStatus + + + Name + Nome + + + + Novel File Status Levels + Níves de Estado do Arquivo do Livro + + + + Note File Importance Levels + Níves de Importância do Arquivo do Livro + + + + New Item + Novo Item + + + + New + Novo + + + + Delete + Remover + + + + Save + Salvar + + + + Colour + Cor + + + + Select Colour + Selecione a Cor + + + + Cannot delete status item that is in use. + Não é possível remover um item de status que estja em uso. + + + + {0} [{1}] + + + + + GuiProjectLoad + + + novelWriter Project File + Arquivo de Projeto do novelWriter + + + + All Files + Todos os Arquivos + + + + Open Project + Abrir Projeto + + + + Working Title + Nome do projeto + + + + Words + Palavras + + + + Last Opened + Aberto Pela Última Vez + + + + Recently Opened Projects + Projetos Abertos Recentemente + + + + Path + Caminho + + + + New + Novo + + + + Open novelWriter Project + Abrir um Projeto do novelWriter + + + + Remove Entry + Remover Entrada + + + + {0} ({1}) + + + + + Remove '{0}' from the recent projects list? The project files will not be deleted. + Remover {0} da lista de projetos recentes? Os arquivos do projeto não serão removidos. + + + + Remove + Remover + + + + Open + Abrir + + + + Cancel + Cancelar + + + + GuiProjectSettings + + + Project Settings + Configurações do Projeto + + + + Settings + Configurações + + + + Status + Estado + + + + Importance + Importância + + + + Auto-Replace + Substituição Automática + + + + Ok + OK + + + + Cancel + Cancelar + + + + GuiProjectTree + + + Please select a valid location in the tree to add the document. + Por favor, selecione uma localização válida para o documento. + + + + Please select a valid location in the tree to add the folder. + Por favor selecione uma localização válida na árvore para adicionar o diretório. + + + + Did not find anywhere to add the file or folder! + Não foi possível encontrar nenhum lugar para adicionar o arquivo ou diretório! + + + + Cannot add new folder to this item. + Não é possível adicionar um novo diretório a este item. + + + + Maximum folder depth has been reached. + A profundidade máxima de diretórios foi alcançada. + + + + There is currently no Trash folder in this project. + Não exite um diretório de Lixeira neste projeto. + + + + The Trash folder is already empty. + O diretório de Lixeira já está vazio. + + + + Empty Trash + Esvaziar a Lixeira + + + + Delete File + Remover Arquivo + + + + Please delete the content first. + Por favor, remova o conteúdo primeiro. + + + + The item cannot be moved to that location. + O item não pode ser movido para este local. + + + + Cannot delete folder. It is not empty. + Não foi possível remover o diretório. Ele não está vazio. + + + + Recursive deletion is not supported. + Exclusão recursiva não é suportada. + + + + Cannot delete root folder. It is not empty. + Não é possível remover o diretório-raiz. Ele não está vazio. + + + + Label + Rótulo + + + + Words + Palavras + + + + Inc + Incl. + + + + Flags + Opções + + + + Item label + Rótulo do item + + + + Word count + Contagem de palavras + + + + Include in build + Incluído na construção + + + + Status, class, and layout flags + Opções de estado, classe e leiaute + + + + New File + Novo Arquivo + + + + New Folder + Novo Diretório + + + + Cannot add new files or folders to the {0} folder. + Não foi possível adicionar novos arquivos ou diretórios ao diretório {0}. + + + + Permanently delete {0} file(s) from Trash? + Permanentemente remover {0} arquivo(s) da Lixeira? + + + + Permanently delete file '{0}'? + Permanentemente remover o arquivo '{0}'? + + + + Move file '{0}' to Trash? + Mover o arquivo '{0}' para a Lixeira? + + + + There is nowhere to add item with name '{0}' + Não existe nenhum lugar para adicionar o item com o nome '{0}' + + + + GuiProjectTreeMenu + + + Edit Project Item + Editar Item do Projeto + + + + Open Document + Abrir Documento + + + + View Document + Ver Documento + + + + Toggle Included Flag + Alternar Opção de Inclusão + + + + New File + Novo Arquivo + + + + New Folder + Novo Diretório + + + + Delete Item + Remover Item + + + + Empty Trash + Esvaziar a Lixeira + + + + Move Item Up + Mover Item Acima + + + + Move Item Down + Mover Item Abaixo + + + + GuiTheme + + + Could not load theme config file. + Não foi posível carregar o arquivo de configuração de tema. + + + + Could not load syntax file. + Não foi possível carregar o arquivo de sintaxe. + + + + GuiWordList + + + Project Word List + Lista de Palavras do Projeto + + + + Add new entry + Adiciona uma nova entrada + + + + Delete selected entry + Remove a entrada selecionada + + + + Cannot add a blank word. + Não é possível adicionar uma palavra em branco. + + + + The word '{0}' is already in the word list. + A palavra '{0}' já está na lista de palavras. + + + + Save + Salvar + + + + Close + Fechar + + + + GuiWritingStats + + + JSON Data File + Aruivo de Dados JSON + + + + CSV Data File + Arquivo de Dados CSV + + + + Writing Statistics + Estatísticas de Escrita + + + + Session Start + Início da Sessão + + + + Length + Tamanho + + + + Words + Palavras + + + + Histogram + Histograma + + + + Sum Totals + Soma dos Totais + + + + Total Time + Tempo Total + + + + Filtered Time + Tempo Filtrado + + + + Novel Word Count + Contagem de Palavras do Livro + + + + Notes Word Count + Contagem de Palavras das Notas + + + + Total Word Count + Contagem Total de Palavras + + + + Filters + Filtros + + + + Count novel files + Contagem dos arquivos do livro + + + + Count note files + Contagem dos arquvos de notas + + + + Hide zero word count + Ocultar contagem zerada de palavras + + + + Hide negative word count + Ocultar contagem negativa de palavras + + + + Group entries by day + Agrupar entradas por dia + + + + Word count cap for the histogram + Limite de quantidade de palavras no histograma + + + + Save As + Salvar Como + + + + Save Document As + Salvar Documento Como + + + + Failed to read session log file. + Houve uma falha ao ler o arquivo de log da sessão. + + + + {0}: + + + + + {0} ({1}) + + + + + Close + Fechar + + + + Idle + Ocioso + + + + Idle Time + Tempo Ocioso + + + + Show idle time + Mostrar tempo ocioso + + + + ISO + + + Afar + Afar + + + + Abkhazian + Abcázio + + + + Avestan + Avéstico + + + + Afrikaans + Africânder + + + + Akan + Akan + + + + Amharic + Amárico + + + + Aragonese + Aragonês + + + + Arabic + Árabe + + + + Assamese + Assamês + + + + Avaric + Avárico + + + + Aymara + Aimará + + + + Azerbaijani + Azerbaijano + + + + Bashkir + Basquir + + + + Belarusian + Bielorrusso + + + + Bulgarian + Búlgaro + + + + Bihari languages + Línguas biaris + + + + Bislama + Bislamá + + + + Bambara + Bambara + + + + Bengali + Bengali + + + + Tibetan + Tibetano + + + + Breton + Bretão + + + + Bosnian + Bósnio + + + + Catalan + Catalão + + + + Chechen + Tchecheno + + + + Chamorro + Chamorro + + + + Corsican + Corso + + + + Cree + Cree + + + + Czech + Tcheco + + + + Church Slavic + Eslavo eclesiástico + + + + Chuvash + Tchuvache + + + + Welsh + Galês + + + + Danish + Dinamarquês + + + + German + Alemão + + + + Divehi + Divehi + + + + Dzongkha + Dzongkha + + + + Ewe + Éwé + + + + Modern Greek + Grego moderno + + + + English + Inglês + + + + Esperanto + Esperanto + + + + Spanish + Espanhol + + + + Estonian + Estoniano + + + + Basque + Basco + + + + Persian + Persa + + + + Fulah + Fula + + + + Finnish + Finlandês + + + + Fijian + Fidjiano + + + + Faroese + Feroês + + + + French + Francês + + + + Western Frisian + Frisão ocidental + + + + Irish + Irlandês + + + + Gaelic + Gaélico escocês + + + + Galician + Galego + + + + Guarani + Guarani + + + + Gujarati + Gujarati + + + + Manx + Manês + + + + Hausa + Hauçá + + + + Hebrew + Hebraico + + + + Hindi + Hindi + + + + Hiri Motu + Miri Motu + + + + Croatian + Croata + + + + Haitian + Crioulo haitiano + + + + Hungarian + Húngaro + + + + Armenian + Armênio + + + + Herero + Hereró + + + + Interlingua + Interlíngua + + + + Indonesian + Indonésio + + + + Interlingue + Interlíngua + + + + Igbo + Ibo + + + + Sichuan Yi + Yi de Sichuan + + + + Inupiaq + Inupiaq + + + + Ido + Ido + + + + Icelandic + Islandês + + + + Italian + Italiano + + + + Inuktitut + Inuktitut + + + + Japanese + Japonês + + + + Javanese + Javanês + + + + Georgian + Georgiano + + + + Kongo + Kongo + + + + Kikuyu + Kikuyu + + + + Kuanyama + Oshikwanyama + + + + Kazakh + Cazaque + + + + Kalaallisut + Groenlandês + + + + Central Khmer + Khmer + + + + Kannada + Canarês + + + + Korean + Coreano + + + + Kanuri + Kanuri + + + + Kashmiri + Caxemir + + + + Kurdish + Curdo + + + + Komi + Komi + + + + Cornish + + + + + Kirghiz + Quirguiz + + + + Latin + Latim + + + + Luxembourgish + Luxemburguês + + + + Ganda + Luganda + + + + Limburgan + Limburguês + + + + Lingala + Lingala + + + + Lao + Laociano + + + + Lithuanian + Lituano + + + + Luba-Katanga + Luba-Katanga + + + + Latvian + Letão + + + + Malagasy + Magalaxe + + + + Marshallese + Marshallês + + + + Maori + Maori + + + + Macedonian + Macedônio + + + + Malayalam + Malaiala + + + + Mongolian + Mongol + + + + Marathi + Marata + + + + Malay + Malaio + + + + Maltese + Maltês + + + + Burmese + Birmanês + + + + Nauru + Nauru + + + + North Ndebele + Ndebele do norte + + + + Nepali + Nepali + + + + Ndonga + Ndonga + + + + Dutch + Holandês + + + + Norwegian Nynorsk + Novo norueguês + + + + Norwegian + Norueguês + + + + South Ndebele + Ndebele do sul + + + + Navajo + Navajo + + + + Chichewa + Nianja + + + + Occitan + Occitano + + + + Ojibwa + Chippewa + + + + Oromo + Oromo + + + + Oriya + Oriá + + + + Ossetian + Oseto + + + + Panjabi + Panjabi + + + + Pali + Páli + + + + Polish + Polaco + + + + Pushto + Pachto + + + + Portuguese + Português + + + + Quechua + Quíchua + + + + Romansh + Reto-romano + + + + Rundi + Kirundi + + + + Romanian + Romeno + + + + Russian + Russo + + + + Kinyarwanda + Quiniaruanda + + + + Sanskrit + Sânscrito + + + + Sardinian + Sardo + + + + Sindhi + Sindi + + + + Northern Sami + Sami do norte + + + + Sango + Sango + + + + Sinhala + Cingalês + + + + Slovak + Eslovaco + + + + Slovenian + Esloveno + + + + Samoan + Samoano + + + + Shona + Chona + + + + Somali + Somali + + + + Albanian + Albenês + + + + Serbian + Sérvio + + + + Swati + Suázi + + + + Southern Sotho + Soto do sul + + + + Sundanese + Sundanês + + + + Swedish + Sueco + + + + Swahili + Suaíli + + + + Tamil + Tâmil + + + + Telugu + Telugu + + + + Tajik + Takique + + + + Thai + Tailandês + + + + Tigrinya + Tigrínia + + + + Turkmen + Turcomano + + + + Tagalog + Tagalo + + + + Tswana + Tswana + + + + Tonga + Tonga + + + + Turkish + Turco + + + + Tsonga + Tsonga + + + + Tatar + Tártaro + + + + Twi + Twi + + + + Tahitian + Taitiano + + + + Uighur + Uigur + + + + Ukrainian + Ucraniano + + + + Urdu + Urdu + + + + Uzbek + Uzbeque + + + + Venda + Venda + + + + Vietnamese + Vietnamita + + + + Walloon + Valão + + + + Wolof + Wolof + + + + Xhosa + Xhosa + + + + Yiddish + Iídiche + + + + Yoruba + Iorubá + + + + Zhuang + Zhuang + + + + Chinese + Chinês + + + + Zulu + Zulo + + + + Andorra + Andorra + + + + United Arab Emirates + Emirados Árabes Unidos + + + + Afghanistan + Afeganistão + + + + Antigua and Barbuda + Antígua e Barbuda + + + + Anguilla + Anguilla + + + + Albania + Albânia + + + + Armenia + Armênia + + + + Angola + Angola + + + + Antarctica + Antártida + + + + Argentina + Argentina + + + + American Samoa + Samoa Americana + + + + Austria + Áustria + + + + Australia + Austrália + + + + Aruba + Aruba + + + + Azerbaijan + Azerbaijão + + + + Bosnia and Herzegovina + Bósnia e Herzegovina + + + + Barbados + Barbados + + + + Bangladesh + Bangladesh + + + + Belgium + Bélgica + + + + Burkina Faso + Burkina Faso + + + + Bulgaria + Bulgária + + + + Bahrain + Barém + + + + Burundi + Burundi + + + + Benin + Benim + + + + Bermuda + Bermudas + + + + Brunei Darussalam + Brunei + + + + Plurinational State of Bolivia + Bolívia + + + + Sint Eustatius and Saba Bonaire + Bonaire, Santo Eustáquio e Saba + + + + Brazil + Brasil + + + + Bahamas + Bahamas + + + + Bhutan + Butão + + + + Bouvet Island + Ilha Bouvet + + + + Botswana + Botswana + + + + Belarus + Bielorrússia + + + + Belize + Belize + + + + Canada + Canadá + + + + Cocos (Keeling) Islands + Ilhas Cocos + + + + The Democratic Republic of the Congo + República Democrática do Congo + + + + Central African Republic + República Centro-Africana + + + + Congo + República do Congo + + + + Switzerland + Suíça + + + + Cook Islands + Ilhas Cook + + + + Chile + Chile + + + + Cameroon + Camarões + + + + China + China + + + + Colombia + Colômbia + + + + Costa Rica + Costa Rica + + + + Cuba + Cuba + + + + Cape Verde + Cabo Verde + + + + Christmas Island + Ilha Christmas + + + + Cyprus + Chipre + + + + Czech Republic + República Tcheca + + + + Germany + Alemanha + + + + Djibouti + Djibouti + + + + Denmark + Dinamarca + + + + Dominica + Domínica + + + + Dominican Republic + República Dominicana + + + + Algeria + Algeria + + + + Ecuador + Equador + + + + Estonia + Estônia + + + + Egypt + Egito + + + + Western Sahara + Saara Ocidental + + + + Eritrea + Eritreia + + + + Spain + Espanha + + + + Ethiopia + Etiópia + + + + Finland + Finlândia + + + + Fiji + Fiji + + + + Falkland Islands (Malvinas) + Ilhas Malvinas + + + + Federated States of Micronesia + Estados Federados da Micronésia + + + + Faroe Islands + Ilhas Feroé + + + + France + França + + + + Gabon + Gabão + + + + United Kingdom + Reino Unido da Grã-Bretanha e Irlanda do Norte + + + + Grenada + Granada + + + + Georgia + Geórgia + + + + French Guiana + Guiana Francesa + + + + Guernsey + Guernsey + + + + Ghana + Gana + + + + Gibraltar + Gibraltar + + + + Greenland + Groenlândia + + + + Gambia + Gâmbia + + + + Guinea + Guiné-Conacri + + + + Guadeloupe + GUadalupe + + + + Equatorial Guinea + Guiné Equatorial + + + + Greece + Grécia + + + + South Georgia and the South Sandwich Islands + Ilhas Geórgia do Sul e Sandwich do Sul + + + + Guatemala + Guatemala + + + + Guam + Guam + + + + Guinea-Bissau + Guiné-Bissau + + + + Guyana + Guiana + + + + Hong Kong + Hong Kong + + + + Heard Island and McDonald Islands + Ilha Heard e Ilhas McDonald + + + + Honduras + Honduras + + + + Croatia + Croácia + + + + Haiti + Haiti + + + + Hungary + Hungria + + + + Indonesia + Indonésia + + + + Ireland + Irlanda + + + + Israel + Israel + + + + Isle of Man + Ilha de Man + + + + India + Índia + + + + British Indian Ocean Territory + Território Britânico do Oceano Índico + + + + Iraq + Iraque + + + + Islamic Republic of Iran + Irã + + + + Iceland + Islândia + + + + Italy + Itália + + + + Jersey + Jersey + + + + Jamaica + Jamaica + + + + Jordan + Jordânia + + + + Japan + Japão + + + + Kenya + Quênia + + + + Kyrgyzstan + Quirguistão + + + + Cambodia + Cambodja + + + + Kiribati + Kiribati + + + + Comoros + Comores + + + + Saint Kitts and Nevis + São Cristóvão e Névis + + + + Democratic People's Republic of Korea + Coreia do Norte + + + + Republic of Korea + Coreia do Sul + + + + Kuwait + Kuwait + + + + Cayman Islands + Ilhas Cayman + + + + Kazakhstan + Cazaquistão + + + + Lao People's Democratic Republic + Laos + + + + Lebanon + Líbano + + + + Saint Lucia + Santa Lúcia + + + + Liechtenstein + Liechtenstein + + + + Sri Lanka + Sri Lanka + + + + Liberia + Libéria + + + + Lesotho + Lesoto + + + + Lithuania + Lituânia + + + + Luxembourg + Luxemburgo + + + + Latvia + Letônia + + + + Libya + Líbia + + + + Morocco + Marrocos + + + + Monaco + Mônaco + + + + Republic of Moldova + Moldávia + + + + Montenegro + Montenegro + + + + Saint Martin (French part) + São Martinho (França) + + + + Madagascar + Madagáscar + + + + Marshall Islands + Ilhas Marshall + + + + The Former Yugoslav Republic of Macedonia + Macedônia do Norte + + + + Mali + Mali + + + + Myanmar + Myanmar + + + + Mongolia + Mongólia + + + + Macao + Macau + + + + Northern Mariana Islands + Marianas Setentrionais + + + + Martinique + Martinica + + + + Mauritania + Mauritânia + + + + Montserrat + Montserrat + + + + Malta + Malta + + + + Mauritius + Maurícia + + + + Maldives + Maldivas + + + + Malawi + Malawi + + + + Mexico + México + + + + Malaysia + Malásia + + + + Mozambique + Moçambique + + + + Namibia + Namíbia + + + + New Caledonia + Nova Caledônia + + + + Niger + Níger + + + + Norfolk Island + Ilha Norfolk + + + + Nigeria + Nigéria + + + + Nicaragua + Nicarágua + + + + Netherlands + Holanda + + + + Norway + Noruega + + + + Nepal + Nepal + + + + Niue + Niue + + + + New Zealand + Nova Zelândia + + + + Oman + Oman + + + + Panama + Panamá + + + + Peru + Peru + + + + French Polynesia + Polinésia Francesa + + + + Papua New Guinea + Papua-Nova Guiné + + + + Philippines + Filipinas + + + + Pakistan + Paquistão + + + + Poland + Polônia + + + + Saint Pierre and Miquelon + Saint Pierre et Miquelon + + + + Pitcairn + Pitcairn + + + + Puerto Rico + Porto Rico + + + + State of Palestine + Palestina + + + + Portugal + Portugal + + + + Palau + Palau + + + + Paraguay + Paraguai + + + + Qatar + Catar + + + + Romania + Romênia + + + + Serbia + Sérvia + + + + Russian Federation + Rússia + + + + Rwanda + Ruanda + + + + Saudi Arabia + Arábia Saudita + + + + Solomon Islands + Ilhas Salomão + + + + Seychelles + Ilhas Seychelles + + + + Sudan + Sudão + + + + Sweden + Suécia + + + + Singapore + Singapura + + + + Saint Helena, Ascension and Tristan da Cunha + Santa Helena + + + + Slovenia + Eslovénia + + + + Svalbard and Jan Mayen + Svalbard e Jan Mayen + + + + Slovakia + Eslováquia + + + + Sierra Leone + Serra Leoa + + + + San Marino + San Marino + + + + Senegal + Senegal + + + + Somalia + Somália + + + + Suriname + Suriname + + + + South Sudan + Sudão do Sul + + + + Sao Tome and Principe + São Tomé e Príncipe + + + + El Salvador + El Salvador + + + + Sint Maarten + São Martinho + + + + Syrian Arab Republic + Síria + + + + Swaziland + Essuatíni + + + + Turks and Caicos Islands + Turks e Caicos + + + + Chad + Chade + + + + French Southern Territories + Terras Austrais e Antárticas Francesas + + + + Togo + Togo + + + + Thailand + Tailândia + + + + Tajikistan + Tajiquistão + + + + Tokelau + Toquelau + + + + Timor-Leste + Timor-Leste + + + + Turkmenistan + Turcomenistão + + + + Tunisia + Tunísia + + + + Turkey + Turquia + + + + Trinidad and Tobago + Trinidade e Tobago + + + + Tuvalu + Tuvalu + + + + Taiwan, Province of China + Taiwan + + + + United Republic of Tanzania + Tanzânia + + + + Ukraine + Ucrânia + + + + Uganda + Uganda + + + + United States Minor Outlying Islands + Ilhas Menores Distantes dos Estados Unidos + + + + United States + Estados Unidos + + + + Uruguay + Uruguai + + + + Uzbekistan + Usbequistão + + + + Holy See (Vatican City State) + Vaticano + + + + Saint Vincent and the Grenadines + São Vicente e Granadinas + + + + Bolivarian Republic of Venezuela + Venezuela + + + + British Virgin Islands + Ilhas Virgens Britânicas + + + + U.S. Virgin Islands + Ilhas Virgens Americanas + + + + Viet Nam + Vietnam + + + + Vanuatu + Vanuatu + + + + Wallis and Futuna + Wallis e Futuna + + + + Samoa + Samoa + + + + Yemen + Iémen + + + + Mayotte + Mayotte + + + + South Africa + África do Sul + + + + Zambia + Zâmbia + + + + Zimbabwe + Zimbabwe + + + + Norwegian Bokmu0229l + Bokm\u0229l Norueguês + + + + u0197land Islands + Ilhas \u0197land + + + + Cu00f4te d'Ivoire + Costa do Marfim + + + + Volapu00fck + Volapuque + + + + Saint Barthu00e9lemy + São Bartolomeu + + + + Curau00e7ao + Curaçau + + + + Ru00e9union + Reunião + + + + NWDoc + + + Failed to open document file. + Houve uma falha ao abrir o arquivo do documento. + + + + Opened Document + Documento Aberto + + + + Could not save document. + Não foi possível salvar o documento. + + + + Saved Document + Documento Salvo + + + + Could not delete document file. + Não foi possível remover o arquivo do documento. + + + + {0}: {1} + + + + + NWErrorMessage + + + Close + Fechar + + + + NWIndex + + + The project index is outdated or broken. Rebuilding index. + O índice do projeto está desatualizado ou quebrado. Reconstruíndo o indice. + + + + NWProject + + + Trash + Lixeira + + + + Chapter %ch%: %title% + Capítulo %ch%: %title% + + + + New + Novo + + + + Note + Nota + + + + Draft + Rascunho + + + + Finished + Finalizado + + + + Minor + Menor + + + + Major + Maior + + + + Main + Principal + + + + New Project + Novo Projeto + + + + By + Por + + + + Novel + Livro + + + + Plot + Enredo + + + + Characters + Personagens + + + + World + Mundo + + + + Title Page + Página de Título + + + + New Chapter + Novo Capítulo + + + + New Scene + Nova Cena + + + + Failed to parse project xml. + Houve uma falha ao interpretar o conteúdo XML do projeto. + + + + Attempting to open backup project file instead. + Tentando abrir a cópia de segurança do projeto. + + + + Unknown + Desconhecido + + + + Project file does not appear to be a novelWriterXML file. + O arquivo do projeto não parece ser um arquivo XML do novelWriter. + + + + Old Project Version + Versão Antiga de Projeto + + + + The project file and data is created by a novelWriter version lower than 0.7. Do you want to upgrade the project to the most recent format? + O arquivo do projeto e os dados foram criados por uma versão anterior à 0.7 do novelWriter. Deseja atualizar o projeto para o formato mais recente? + + + + Note that after the upgrade, you cannot open the project with an older version of novelWriter any more, so make sure you have a recent backup. + Observe que após a atualização, não será possível abrir o projeto com uma versão mais antiga do novelWriter, tenha certeza de ter uma cópia de segurança recente. + + + + Version Conflict + Conflito de Versão + + + + This project was saved by a newer version of novelWriter, version {new_version}. This is version {version}. If you continue to open the project, some attributes and settings may not be preserved, but the overall project should be fine. Continue opening the project? + O projeto foi salvo por uma versão mais nova do novelWriter, versão {new_version}. Esta é a versão {version}. Caso deseje continuar a abrir o projeto, alguns atributos e configurações podem não ser preservados, o restante do projeto, porém, deve funcionar. Continuar a abrir o projeto? + + + + Opened Project + Projeto Aberto + + + + Project path not set, cannot save project. + O caminho do projeto não foi definido, não é possível salvar o projeto. + + + + Failed to save project. + Houve uma falha ao salvar o projeto. + + + + Saved Project + Projeto Salvo + + + + Backing up project ... + Realizando uma cópia de segurança do projeto... + + + + Cannot backup project because no backup path is set. Please set a valid backup location in Tools > Preferences. + Não foi possível realizar uma cópia de segurança do projeto porquê o caminho das cópias de segurança não foi definido. Por favor, defina um caminho válido para as cópias de segurança em Ferramentas > Preferências. + + + + Cannot backup project because no project name is set. Please set a Working Title in Project > Project Settings. + Não foi possível realizar a cópia de segurança do projeto porque o nome do projeto não está definido. Por favor defina o Nome do Projeto em Projeto > Configurações do Projeto. + + + + Cannot backup project because the backup path does not exist. Please set a valid backup location in Tools > Preferences. + Não foi possível realizar a cópia de segurança do projeto porque o caminho das cópias de segurança não exite. Por favor, defina um cainho válido para as cópias de segurança em Ferramentas > Preferências. + + + + Could not create backup folder. + Não foi possível ler o diretório de cópias de segurança. + + + + Cannot backup project because the backup path is within the project folder to be backed up. Please choose a different backup path in Tools > Preferences. + Não foi possível realizar a cópia de segurança do projeto porque o caminho das cópias de segurança está em um caminho dentro do diretório do projeto. Por favor, escolha um caminho diferente para as cópias de segurança em Ferramentas> Preferências. + + + + Could not write backup archive. + Não foi possível escrever o arquivo da cópia de segurança. + + + + Failed to create a new example project. + Houve uma falha ao criar um novo projeto de exemplo. + + + + Failed to create a new example project. Could not find the necessary files. They seem to be missing from this installation. + Houve uma falha ao criar um novo projeto de exemplo. Não foi possível encontrar os arquivos necessários. Eles parecem estar faltando nesta instalação. + + + + Could not create new project folder. + Não foi possível criar o diretório do novo projeto. + + + + New project folder is not empty. Each project requires a dedicated project folder. + O diretório do novo projeto não está vazio. Cada projeto requer um diretório dedicado. + + + + You must set a valid backup path in preferences to use the automatic project backup feature. + Deve ser definido um caminho válido para as cópias de segurança nas preferências para usar a funcionalidade de cópias de segurança automáticas. + + + + You must set a valid project name in project settings to use the automatic project backup feature. + Deve ser definido um nome de projeto válido nas preferências do projeto para usar a funcionalidade de cópias de segurança automáticas. + + + + Recovered + Recuperado + + + + One or more orphaned files could not be added back into the project. Make sure at least a Novel root folder exists. + Um ou mais arquivos-órfãos não puderam ser readicionados ao projeto. Verifique que pelo menos um diretório-raiz de Livro exista. + + + + Start Time + Hora de Início + + + + End Time + Hora de Término + + + + Notes + Notas + + + + Moved file + Arquivo movido + + + + New location + Novo local + + + + Could not move + Não foi possível mover + + + + Could not delete + Não foi possível remover + + + + Failed to remove + Houve uma falha ao remover + + + + Could not make folder + Não foi possível criar o diretório + + + + sample + amostra + + + + content + conteúdo + + + + junk + lixo + + + + Chapter {0} + Capítulo {0} + + + + Scene {0} + Cena {0} + + + + File not found: {0} + Arquivo não encontrado: {0} + + + + {0}: {1} + + + + + Backup from {0} + Cópia de segurança de {0} + + + + Backup archive file written to: {0} + Arquivo da cópia de segurança escrito em: {0} + + + + Project backed up to '{0}' + Cópia de segurança realizada para '{0}' + + + + Found {0} orphaned file(s) in project folder. + Foram encontrados {0} arquivos-órfãos no diretório do projeto. + + + + {0}: + + + + + Recovered File {0} + Arquivo Recuperado {0} + + + + Offset {0} + Deslocamento {0} + + + + Not a folder: {0} + Não é um diretório: {0} + + + + Could not move item {0} to junk. + Não foi possível mover o item {0} para o lixo. + + + + Unknown or unsupported novelWriter project file format. The project cannot be opened by this version of novelWriter. The file was saved with novelWriter version {0}. + Format de arquivo de projeto do novelWriter desconhecido ou não-suportado. O projeto não pode ser aberto por essa versão do novelWriter. O arquivo foi salvo com a versão {0} do novelWriter. + + + + Idle + Ocioso + + + + NWTree + + + Table of Contents + Sumário + + + + File Name + Nome do Arquivo + + + + Class + Classe + + + + Layout + Leiaute + + + + Document Label + Rótulo do Documento + + + + ProjWizardCustomPage + + + Custom Project Options + Opções Personalizadas do Projeto + + + + Select which additional root folders to make, and how to populate the Novel folder. If you don't want to add chapters or scenes, set the values to 0. You can add scenes without chapters. + Selecione quais diretórios-raiz adicionais criar e como popular o diretório do livro. Se você não quiser adicionar capítulos ou cenas, deixe os valores em 0. Você pode adicionar cenas sem capítulos. + + + + Additional Root Folders + Diretórios-raiz adicionais + + + + Populate Novel Folder + Popular Diretório do Livro + + + + Add chapters + Adicionar capítulos + + + + Scenes (per chapter) + Cenas (por capítulo) + + + + Add chapter folders + Adicionar diretórios de capítulo + + + + {0} folder + diretório {0} + + + + ProjWizardFinalPage + + + Finished + Finalizado + + + + All done. + Tudo pronto. + + + + Done + Pronto + + + + Finish + Terminar + + + + Press '{0}' to create the new project. + Pressione '{0}' para criar um novo projeto. + + + + ProjWizardFolderPage + + + Select Project Folder + Selecione o Diretório do Projeto + + + + Select a location to store the project. A new project folder will be created in the selected location. + Selecione o local para armazenar o projeto. Um novo diretório será criado para o projeto no local selecionado. + + + + Required + Obrigatório + + + + Project Path + Caminho do projeto + + + + ProjWizardIntroPage + + + Provide at least a working title. The working title should not be change beyond this point as it is used by the application for generating file names for for instance backups. The other fields are optional and can be changed at any time in Project Settings. + Forneça pelo menos um nome de projeto. O nome do projeto não poderá ser modificado após esse ponto pois é utilizado pela aplicação para a geração de nomes de arquivos e para cópias de segurança. Os outros campos são opcionais e podem ser modificados a qualquer momento nas Configurações do Projeto. + + + + Side image by {author:s}, {license:s} + Imagem ao lado por {author:s}, {license:s} + + + + Create New Project + Criar um Projeto Novo + + + + Required + Obrigatório + + + + Optional + Opcional + + + + Optional. One name per line. + Opcional. Um nome por linha. + + + + Working Title + Nome do projeto + + + + Novel Title + Nome do Livro + + + + Author(s) + Autor(es) + + + + ProjWizardPopulatePage + + + Populate Project + Popular o Projeto + + + + Choose how to pre-fill the project. Either with a minimal set of starter items, an example project explaining and showing many of the features, or show further custom options on the next page. + Escolha como pré-popular o projeto. escolha entre um conjunto mínimo de items iniciais, um projeto de exemplo explicando e mostrando várias das funcionalidades ou escolha opções personalizadas na próxima página. + + + + Fill the project with a minimal set of items + Popular o projeto com um conjunto mínimo de items + + + + Fill the project with example files + Popular o projeto com arquivos de exemplo + + + + Show detailed options for filling the project + Mostrar opções detalhadas para popular o projeto + + + + QuotesDialog + + + Ok + OK + + + + Cancel + Cancelar + + + + ToHtml + + + Synopsis + Sinopse + + + + Comment + Comentário + + + + Tokenizer + + + Notes + Notas + + + + Document '{doc_name}' is too big ({doc_size}). Skipping. + O documento '{doc_name}' é muito grande ({doc_size}). Ignorando. + + + + ERROR + ERRO + + + + {0}: {1} + + + + diff --git a/nw/languages/phrases_pt.qph b/nw/languages/phrases_pt.qph new file mode 100644 index 00000000..7b58cb0b --- /dev/null +++ b/nw/languages/phrases_pt.qph @@ -0,0 +1,447 @@ + + + + Point of View + Ponto de Vista + + + Characters + Personagens + + + Plot + Enredo + + + Timeline + Linha do Tempo + + + Locations + Lugares + + + Objects + Objetos + + + Entities + Entidades + + + None + Nenhum + + + Novel + Livro + + + Entity + Entidade + + + Outtakes + Removidos + + + Trash + Lixeira + + + Title Page + Página de Título + + + Book + Livro + + + Plain Page + Página + + + Partition + Partição + + + Unnumbered + Sem Numeração + + + Scene + Cena + + + Note + Nota + + + Title + Título + + + Level + Nível + + + Document + Documento + + + Line + Linha + + + Chars + Caracteres + + + Words + Palavras + + + Synopsis + Sinopse + + + About + Sobre + + + Release + Versões + + + About novelWriter + Sobre o novelWriter + + + Credits + Créditos + + + Author + Autor + + + Credit + Créditos + + + License + Licença + + + Theme + Tema + + + Icons + Ícones + + + Syntax + Sintaxe + + + Website + Website + + + Chapter + Capítulo + + + Section + Seção + + + Font family + Família da fonte + + + Font size + Tamanho da fonte + + + Justify text + Texto justificado + + + Print + Imprimir + + + Build Project + Construir o Projeto + + + Save As + Salvar Como + + + Close + Fechar + + + Plain Text + Texto Simples + + + Plain HTML + HTML Simples + + + Save Document As + Salvar Documento Como + + + Unknown + Desconhecido + + + Look and Feel + Aparência + + + Project Backup + Cópia de Segurança + + + Path + Caminho + + + Status + Estado + + + Replace + Substituir + + + Search + Pesquisa + + + Handle + Referência + + + References + Referências + + + Label + Rótulo + + + Class + Classe + + + Layout + Leiaute + + + Characters + Caracteres + + + Editor + Editor + + + Project + Projeto + + + Provider + Provedor + + + unknown + desconhecido + + + Paragraphs + Parágrafos + + + Default + Padrão + + + Working title + Nome do projeto + + + Project path + Caminho do projeto + + + Project Stats + Estatíticas do Projeto + + + Folders + Diretórios + + + Documents + Documentos + + + Word count + Contagem de palavras + + + Keyword + Palavra-chave + + + New + Novo + + + Delete + Remover + + + Save + Salvar + + + Name + Nome + + + New Item + Novo Item + + + Last Opened + Aberto Pela Última Vez + + + Settings + Configurações + + + Details + Detalhes + + + Importance + Importância + + + Auto-Replace + Substituir automaticamente + + + Flag + Opção + + + Flags + Opções + + + (New Entry) + + + + New File + Novo Arquivo + + + New Folder + Novo Diretório + + + Histogram + Histograma + + + Finished + Finalizado + + + Done + Pronto + + + Finish + Terminar + + + Auto-Replace + Substituição Automática + + + No Suggestions + Sem Sugestões + + + Browse + Procurar + + + Tag + Etiqueta + + + Draft + Rascunho + + + Minor + Menor + + + Major + Maior + + + Backup + Cópia de Segurança + + + Undo + Desfazer + + + Release + Lançamento + + + Pages + Páginas + + + Page + Página + + + Progress + Progresso + + + Chapters + Capítulos + + + Scenes + Cenas + + + Revisions + Revisões + + + seconds + segundos + + diff --git a/setup.py b/setup.py index c9c9d302..00564997 100755 --- a/setup.py +++ b/setup.py @@ -186,6 +186,20 @@ def buildQtDocs(): return +def buildQtI18n(): + try: + subprocess.call(["lrelease", "-verbose", "novelWriter.pro"]) + except Exception as e: + print("QtI18n Release Error:") + print(str(e)) + +def buildQtI18nTS(): + try: + subprocess.call(["pylupdate5", "-verbose", "-noobsolete", "novelWriter.pro"]) + except Exception as e: + print("QtI18n Release Error:") + print(str(e)) + ## # Sample Project ZIP File Builder (sample) ## @@ -898,6 +912,14 @@ if __name__ == "__main__": sys.argv.remove("qthelp") buildQtDocs() + if "qtlrelease" in sys.argv: + sys.argv.remove("qtlrelease") + buildQtI18n() + + if "qtlupdate" in sys.argv: + sys.argv.remove("qtlupdate") + buildQtI18nTS() + if "sample" in sys.argv: sys.argv.remove("sample") buildSampleZip()