diff --git a/README.md b/README.md index 91e4102e..84a373a3 100644 --- a/README.md +++ b/README.md @@ -70,9 +70,13 @@ code, please also read the full ### Translations -New translations are always welcome. This project uses crowdin to maintain translations, and you -can contribute translations at the [crowdin project page](https://crowdin.com/project/novelwriter). -See also the [Translations of novelWriter](https://github.com/vkbo/novelWriter/issues/93). +New translations are always welcome. This project uses Crowdin to maintain translations, and you +can contribute translations at the [Crowdin project page](https://crowdin.com/project/novelwriter). +If you have any questions, feel free to post them to the +[Translations of novelWriter](https://github.com/vkbo/novelWriter/issues/93) issue thread. + +For more details, and how to use Qt Linguist for translations, see the +[i18n instructions](https://github.com/vkbo/novelWriter/blob/main/i18n/README.md). ## Key Features diff --git a/i18n/README.md b/i18n/README.md new file mode 100644 index 00000000..27486cf9 --- /dev/null +++ b/i18n/README.md @@ -0,0 +1,123 @@ +# novelWriter Internationalisation (i18n) + +The maintenance of translations has been moved to the Crowdin service. The translation strings can +be edited there at the [novelWriter project page](https://crowdin.com/project/novelwriter). + +You can still use the manual approach listed below, and then upload the file through the website's +interface. The translation strings for that language will then be updated and queued for approval. + +To verify a language file translated through the Crowdin tool, download and extract it into the +`i18n` folder in the novelWriter source and follow the instructions in +[Generate an Updated Translation File](#generate-an-updated-translation-file) below. + + +# Direct Approach Using Qt Linguist + +Here you will find instructions for translating novelWriter to a new language directly using Qt +Linguist, or updating the translations for an already existing supported language. + +There are two areas relevant to localisation: + +* The Qt5 GUI translation files, which consists of `nw_XX.ts` files. This is the bulk of the + translation work. +* The `project_XX.json` files. See [Project Localisation](#project-localisation) below. + +The `XX` in the file name corresponds to the language and country code for the translation. For +instance `en_GB` for British English. + + +## Qt5 GUI Localisation + +You will need the translation tool Qt 5 Linguist on your system. + +For Ubuntu/Debian, run: +```bash +sudo apt install qttools5-dev-tools +``` + +See also [Qt Linguist Manual: Translators](https://doc.qt.io/qt-5/linguist-translators.html). + + +### Workflow + +Here's an overview of the localisation workflow: + +1. First, create a new branch off of `main`, and there, generate a new `nw_XX.ts` translation file. +1. Load the translation file in Qt Linguist. Translate and save your changes. +1. Generate the `nw_XX.qm` file. +1. Run the application and verify your changes. Iterate from step 2. +1. Once finished, commit your changes and submit your pull request. + +When you want to translate new changes, again create a new branch off of `main` and re-generate the +translation file. Continue from step 2. + + +### Generate an Updated Translation File + +The `i18n` folder at the root of the repository contains the `nw_XX.ts` translation files. + +Whether to add a new language to the translation framework, or to update the file against the +current source code, you must first run the `qtlupdate` command: +```bash +python3 setup.py qtlupdate i18n/nw_XX.ts +``` + +The file name must be in the correct format. All translation files must be located in the `i18n` +folder, start with `nw_` and end with `.ts`. In between goes the language and country code. It must +be a valid ISO language code, otherwise novelWriter will not accept the file. + +For instance, the French translation uses the language code `fr_FR`, so its translation file will +be `nw_fr_FR.ts` + + +### Edit the Translation File in Qt Linguist + +The `i18n/nw_XX.ts` file can be edited with the Qt Linguist application provided by Qt 5. This is +by far easier than manually editing the `.ts` file. + +Please select "English" and "United Kingdom" as the _source_ language if prompted by Qt Linguist. + + +### Verify Your Translation + +The application does not use `.ts` files directly. The `novelwriter/assets/i18n/nw_XX.qm` files are +the actual files used to translate the GUI into another language other than the default British +English. These files are not generated by default, but they can be built with: +```bash +python3 setup.py qtlrelease +``` + +You can now test the translation in novelWriter. The Preferences dialog should list the newly added +language, so go ahead and select it. + + +### Missing QtBase Translations + +The default Qt dialogs also have translations, for instance for standard buttons like "Yes", "No", +"Ok", "Cancel", etc. Generally, these translation files are installed with the Qt libraries on your +system, and novelWriter will collect those translations from there. However, these translations are +missing for many languages. + +As a starting point, there is no need to translate any entries in the `.ts` files that are under +elements starting with the letter "Q", like "QPlatformTheme", "QWizard", etc. If these turn up in +English in novelWriter after activating a translation, it means they are probably missing in the Qt +library, and you may also need to translate these. + +These additional translation entries are generated from a file named `i18n/qtbase.py`, which is not +a file that novelWriter uses. It is there only to generate these additional entries for the `.ts` +files. + + +## Project Localisation + +Projects can have a different language setting than the GUI itself. The files with format +`project_XX.json` in `novelwriter/assets/i18n` are simple translation maps for text that goes into +the exported documents generated by the Build Novel Project tool. + +The files are loaded based on the language setting in the Build Novel Project tool. At the present +time, no other parts of the application use these files. Since these are used as replacement +lookups, they are maintained as plain JSON files. The main usage is to generate chapter headers as +number words. + +Adding new translations for these files is easy. Just copy the `project_en_GB.json` file, rename it +to the appropriate language coded filename, and open it in a text editor and edit the content. diff --git a/i18n/nw_base.ts b/i18n/nw_base.ts index 9981c8b4..8634b848 100644 --- a/i18n/nw_base.ts +++ b/i18n/nw_base.ts @@ -77,286 +77,259 @@ Constant - - - + + None - Novel - - + Plot + - Character + Characters + - Location + Locations - - + Timeline + - Object + Objects + - Entity + Entities - - + Custom - Archive - Trash - - - Characters - - - - - - Locations - - - - - - Objects - - - - - - Entities - - - - - + + Novel Document - - + + Project Note - + Root Folder - + Folder - + Novel Title Page - + Novel Chapter - + Novel Scene - + Tag - + Point of View - - + + Focus - + Title - + Level - + Document - + Line - + Chars - + Words - + Pars - + POV - + Synopsis - + Straight single quotation mark - + Straight double quotation mark - + Left single quotation mark - + Right single quotation mark - + Single low-9 quotation mark - + Single high-reversed-9 quotation mark - + Left double quotation mark - + Right double quotation mark - + Double low-9 quotation mark - + Double high-reversed-9 quotation mark - + Double low-reversed-9 quotation mark - + Single left-pointing angle quotation mark - + Single right-pointing angle quotation mark - + Double left-pointing angle quotation mark - + Double right-pointing angle quotation mark - + Left corner bracket - + Right corner bracket - + Left white corner bracket - + Right white corner bracket diff --git a/novelwriter/constants.py b/novelwriter/constants.py index 50cfc5e4..f71453f3 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -129,19 +129,6 @@ class nwKeyWords: class nwLabels(): CLASS_NAME = { - 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", "Character"), - nwItemClass.WORLD: QT_TRANSLATE_NOOP("Constant", "Location"), - nwItemClass.TIMELINE: QT_TRANSLATE_NOOP("Constant", "Timeline"), - nwItemClass.OBJECT: QT_TRANSLATE_NOOP("Constant", "Object"), - nwItemClass.ENTITY: QT_TRANSLATE_NOOP("Constant", "Entity"), - nwItemClass.CUSTOM: QT_TRANSLATE_NOOP("Constant", "Custom"), - nwItemClass.ARCHIVE: QT_TRANSLATE_NOOP("Constant", "Archive"), - nwItemClass.TRASH: QT_TRANSLATE_NOOP("Constant", "Trash"), - } - CLASS_NAME_LBL = { nwItemClass.NO_CLASS: QT_TRANSLATE_NOOP("Constant", "None"), nwItemClass.NOVEL: QT_TRANSLATE_NOOP("Constant", "Novel"), nwItemClass.PLOT: QT_TRANSLATE_NOOP("Constant", "Plot"), diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index 152cba18..7c1e7e26 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -307,7 +307,7 @@ class NWProject(): nHandle = self.newRoot(self.tr("Novel"), nwItemClass.NOVEL) for newRoot in projData.get("addRoots", []): if newRoot in nwItemClass: - self.newRoot(trConst(nwLabels.CLASS_NAME_LBL[newRoot]), newRoot) + self.newRoot(trConst(nwLabels.CLASS_NAME[newRoot]), newRoot) # Create a title page tHandle = self.newFile(self.tr("Title Page"), nwItemClass.NOVEL, nHandle) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 8105d429..9f55cbe4 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -207,7 +207,7 @@ class GuiProjectTree(QTreeWidget): if itemType == nwItemType.ROOT: tHandle = self.theProject.newRoot( - trConst(nwLabels.CLASS_NAME_LBL[itemClass]), itemClass + trConst(nwLabels.CLASS_NAME[itemClass]), itemClass ) if tHandle is None: logger.error("No root item added") diff --git a/novelwriter/tools/projwizard.py b/novelwriter/tools/projwizard.py index 4b15eb2c..8f746ca6 100644 --- a/novelwriter/tools/projwizard.py +++ b/novelwriter/tools/projwizard.py @@ -310,22 +310,22 @@ class ProjWizardCustomPage(QWizardPage): self.rootGroup.setLayout(self.rootForm) self.lblPlot = QLabel(self.tr("{0} folder").format( - trConst(nwLabels.CLASS_NAME_LBL[nwItemClass.PLOT])) + trConst(nwLabels.CLASS_NAME[nwItemClass.PLOT])) ) self.lblChar = QLabel(self.tr("{0} folder").format( - trConst(nwLabels.CLASS_NAME_LBL[nwItemClass.CHARACTER])) + trConst(nwLabels.CLASS_NAME[nwItemClass.CHARACTER])) ) self.lblWorld = QLabel(self.tr("{0} folder").format( - trConst(nwLabels.CLASS_NAME_LBL[nwItemClass.WORLD])) + trConst(nwLabels.CLASS_NAME[nwItemClass.WORLD])) ) self.lblTime = QLabel(self.tr("{0} folder").format( - trConst(nwLabels.CLASS_NAME_LBL[nwItemClass.TIMELINE])) + trConst(nwLabels.CLASS_NAME[nwItemClass.TIMELINE])) ) self.lblObject = QLabel(self.tr("{0} folder").format( - trConst(nwLabels.CLASS_NAME_LBL[nwItemClass.OBJECT])) + trConst(nwLabels.CLASS_NAME[nwItemClass.OBJECT])) ) self.lblEntity = QLabel(self.tr("{0} folder").format( - trConst(nwLabels.CLASS_NAME_LBL[nwItemClass.ENTITY])) + trConst(nwLabels.CLASS_NAME[nwItemClass.ENTITY])) ) self.addPlot = QSwitch() diff --git a/setup.py b/setup.py index 63445142..378378a4 100755 --- a/setup.py +++ b/setup.py @@ -368,10 +368,9 @@ def buildQtI18nTS(sysArgs): tsList.append(anArg) else: - for aFile in os.listdir("i18n"): - aPath = os.path.join("i18n", aFile) - if os.path.isfile(aPath) and aFile.endswith(".ts"): - tsList.append(aPath) + print("No translation files selected for update ...") + print("") + return for aTS in tsList: print(aTS) @@ -1550,8 +1549,8 @@ if __name__ == "__main__": "", " manual Build the help documentation as PDF (requires LaTeX).", " sample Build the sample project zip file and add it to assets.", - " qtlupdate Update the translation files for internationalisation.", - " To update specific TS files, list them after the command.", + " qtlupdate Update translation files for internationalisation.", + " The files to be updated must be provided as arguments.", " qtlrelease Build the language files for internationalisation.", "", "Python Packaging:",