Extension file Javascript
A Banana Extension is Javascript compliant script files (ECMA-262), containing the Attributes and Javascript code.
The single Javascript extension file, can be distributed as:
- An embedded extension within the accounting file.
Are automatically available within the accounting files. - Installable extensions
Once Extensions are installed you can use with any accounting file.
Extension linked to an accounting file are installed automatically at startup.- As Single file extension that reside on a local computer.
- An extension package that can contains other multiple extensions and other resources.
Javascript and Banana API
You can use any of the supported javascript functions, objects and properties see: Qt ECMAScript Reference.
Banana Accounting also make available the Banana API that you can use to retrieve data or create report objects or else.
Extensions interact with Banana Accounting through some global objects made available by Banana Accounting, like for example 'Banana.document'. Those objects are described under the Banana Script API.
Extension file structure
Extension files have two parts:
- Extension Attributes
Apps Attributes are special formatted JavaScript comment lines, at the beginning of the file. The apps attributes have a left part (name of the attribute) and right part, with the value. Attributes give information about the script, like its purpose, description and so on.- Required attributes are:
- / @api = 1.0
- // @id = ch.banana.apps.example.docfilepath
- // @description = Hello world
- // @task = app.command
- // @doctype = nodocument
- // @publisher = Banana.ch SA
- // @pubdate = 2015-05-12
- Include attribute
The script can include other files with the attribute- // @includejs = other.js
- Required attributes are:
- JavaScript code
The code must be included within functions. Functions are divided in startup functions, settings functions and normal functions.- Startup functions
Are called by Banana software when the script is executed.
The name of the function called depend on the type of the App.- exec() for following types:
- app.command
- export.file
- export.rows
- export.transactions
- import.transactions
- import.rows
- import.accounts
- import.categories
- import.exchangerates
- import.vatcodes
- report.general
- printDocument() for the following types:
- report.customer.invoice
- report.customer.statement
- report.customer.reminder
- exec() for following types:
- settingsDialog() function
It is called by the Banana Software when the user click on the Setting button, relative to the specific app.
The setting data is saved within the Accounting file.
See: Extension's Settings. - Other JavaScript functions
You can write any functions that is necessary.
- Startup functions
Extension id file
The name of this extension must be unique and follow the next rules:
- The @id attribute is used to identify the script;
It is also used by the help system, that open the help page in the browser. - In order to avoid duplicates banana.ch use the following scheme: developer.country.type.name
Value | Description | Example |
---|---|---|
developer | The developer of the app. | ch.banana |
country | The country for which the app is designed. If the app is not designed for a specific country. Use uni as universal. | ch, it, de, ... uni |
type | The type of the app. | app, import, invoice, reminder, statement |
name(*) | The name of the app. | vatreport, voucher,... |
For Invoices as 'name' we use the following scheme: country + number (numbers between 1 and 499):
- Example for Switzerland: ch01, ch02, ch03,...
- Example for all countries: uni01, uni02, uni03,...
For Reminders as 'name' we use the following scheme: country + number (numbers between 500 and 599):
- Example for Switzerland: ch500, ch501, ch502,...
- Example for all countries: uni500, uni501, uni502,...
For Statements as 'name' we use the following scheme: country + number (numbers starting from 600):
- Example for Switzerland: ch600, ch601, ch602,...
- Example for all countries: uni600, uni601, uni602,...
Examples:
- @id = ch.banana.ch.invoice.ch01
- @id = ch.banana.uni.reminder.uni500
- @id = ch.banana.uni.invoice.companyxx
- @id = ch.banana.ch.app.vatreport2018
Example:
ch.banana.ch.extensionexample.js
Extensions types, startup functions and how to run them
The Extension type is defined within the attribute @task.
For each type there is a specific startup function.
See: Exension's types.
The app.command is used for General extensions.
// @task = app.command
Include directive in JavaScript files
JavaScript extension files can use the directive @includejs to include other JavaScript files or use the command .
This included file need to be included in the package. Make sure that all included .js file are also specified in the qrc package description.
// Include a script via @includejs attribute // @includejs = somescript.js" // Include a script via Banana.include() method Banana.include(somescript.js);
Startup function exec([inData, options])
The functio exec() is required by most extensions and in particularly for the General Extensions.
The exec() funciton can have the following arguments:
- inData: the requested input data as a string or a Banana.Document object;
- This parameters is only used for import scripts, for all other tasks this parameter is just null;
- options:
options as an object that can contains those parameters; - useLastSettings: if true the script executes with the last used settings and doesn't show a setting's dialog;
Return value
The exec() function will return a null value or a string.
For import script the format is defined by the attribute @outputformat.
For Productivity extensions it is a JSon document or an Error.
- tablewithheaders
A tab separated text (csv) containing different columns.
The first row is the table header. - transactions.simple
A tab separated text (csv) containing the movement of a account in the form of income and expenses. - Empty type
JSon document that contains documentchange data, that allow to add, modify and delete data within the tables.
See: - @Error:
It an error occurs the function can return a string that begins with "@Error:", for example "@Error:Invalid file format".
The error text is then displayed to the user.
Extension's help and visibility
For marketplace application the Id of the extension is also used to provide context help to the documentation.
Visibility in menu extension
General Extensions are usually visible in the Menu Extension, but the developer can limit the visibility by adding specific properties.
- The attribute @doctype of the file open should match the File that is open.
- // @doctype = 100.110
- The command would be visible only if the accounting file is of type "Double entry with VAT".
- The File Properties > Other > Properties contains a property text that is defined in the property.
By setting the @docproperties in the attribute, an extension can restrict the visibility to only a file that suitable. - The @visibility attribute further allow to limit the visibility.
General Extensions that are part of a package will be visible and started within a Sub-Menu relative to the package.
This way all extensions relative to the package will be grouped together.
Security model
Extensions are secure for the fact that are confined within Banana.
Extensions are NOT ALLOWED to directly write or read file, web resource, change computer setting or execute programs.
Extensions , contrary to Excel Macro, can be run with the confidence, they will not change any data and modify any file or computer settings.
To access or write to file you need to use the Banana Api that display a dialog box to the user.
- To write file you need to use the export functionality, that display a dialog where the user indicate the file name where to save.
- To import file you need to use the import functionality that display a dialog where the user specify the file name.
Extension "Hello World" example
Here an example that open a print preview windows, and show a document with the text "Hello world!!!". Other examples are found in the Extensions tutorial.
// @id = ch.banana.report.helloworld
// @version = 1.0
// @doctype = nodocument
// @publisher = Banana.ch SA
// @description = Hello world
// @task = app.command
// @timeout = -1
function exec() {
//Create the report
var report = Banana.Report.newReport('Report title');
//Add a paragraph with some text
report.addParagraph('Hello World!!!');
//Preview the report
var stylesheet = Banana.Report.newStyleSheet();
Banana.Report.preview(report, stylesheet);
}
Extension with a setting's dialog example
Here an example that use a dialog to input a text. Other examples are found in the Extensions tutorial.
// @id = ch.banana.report.settingsdialog
// @version = 1.0
// @doctype = *
// @publisher = Banana.ch SA
// @description = Example for settings dialog
// @task = app.command
// @timeout = -1
function exec(inData, options) {
// Show dialog if options.useLastSettings is not set or is false
if (!options || !options.useLastSettings) {
if (!settingsDialog())
return; // return if user pressed cancel
}
// Get the settings
var text = Banana.document.getScriptSettings();
//Create the report
var report = Banana.Report.newReport('Report title');
report.addParagraph('You entered: "' + text + '"');
report.addParagraph(new Date().toString());
//Stylesheet
var stylesheet = Banana.Report.newStyleSheet();
//Preview the report
Banana.Report.preview(report, stylesheet);
}
function settingsDialog() {
// Ask the user to enter a text that will be printed in the report
var text = Banana.document.getScriptSettings();
text = Banana.Ui.getText("Enter a text", "The text will be printed in the report", text);
if (typeof(text) === 'string') {
Banana.document.setScriptSettings(text);
return true;
}
return false; // cancel pressed
}
Extension's Attributes
At the beginning at the script there should be a part that define the Extension's Attribute (See Extension File Structure).
// @api = 1.0
// @id = ch.banana.apps.example.docfilepath
// @description = Hello world
// @task = app.command
// @doctype = nodocument
// @publisher = Banana.ch SA
// @pubdate = 2015-05-12
// @inputdatasource = none
// @timeout = -1
The attribute is a commented text line
- Start with //
- Followed by the attribute that start with @
- Followed by the " = " and the value
Tags defines the purpose (import, export, extract, ...), the name displayed in the dialogs, the kind of data it expect to receive, the kind of data it returns, and other information of the script. Tags are inserted at the beginning of the script in comment's lines though the following syntax: "// @tag-name = tag-value".
Attribute list
Attribute name | Required | Value 1) | Description |
@api | Required | The required API version. Available API versions: | Define the required API to be executed in the form of MAIN_VERSION.SUB_VERSION The implemented API in the application have to be equal or newer. See the page API Versions for a list of implemented versions, and the Changelog page for the list of changes in the API. |
@contributors | List of contributors separated by ';' | This attribute contains the list of people that contribute to the developing of the Banana Extensions. | |
@description[.lang] | Required | The name or description of the script | This text will be displayed in the dialogs. This tag is localizable. |
@docproperties | any text | Define a property the script is written for.
It works in combination of the @docproperties and @visibility | |
@doctype | Required | nodocument * XXX.* XXX.YYY !XXX.YYY ... | Define the type of document the script is written for. nodocument = doesn't require an open document, the add-on is always visible
300.* = All productivities App The sign "!" before the file type of the file will exclude the indicate type. The above codes can be combined together like the following examples: 100.130 = for double entries with VAT and with foreign currencies |
@exportfilename | A string defining the name of the file where to export the data. | If the string contains the text <Date>, it will be replaced by the current date in the format of yyyyMMdd-hhmm. | |
@exportfiletype | A string defining the type of data exported txt | This parameter is used for export scripts, it defines the type of exported data and it is used for the extension in the save file dialog. | |
@id | Required | An identification of the script | It is used when setting and reading the preferences. In order to avoid duplicate banana.ch use the following scheme. country.developper.app.domain.name for example: ch.banana.app.patriziato.consuntivopersubtotali |
@includejs | Relative path to a javascript .js file to load before the execution of the script. | Include the javascript file. Every function and object defined in the file are then available to the current script. The application searches for include files in this order:
Each distinct included script is evaluated once, even if it is included more than one time from different scripts. | |
@inputdatasource | One of the following values: none openfiledialog opendirdialog fixedfilepath 2) | With this attribute you can specify if you don't need to input data, if you want the user to select a file to import (openfiledialog), a directory to import (opendirdialog), or if you want to get a file which path is defined in @inputfilepath. If you set fixedfilepath the program will ask the user the permission to open this file, the user's answer will be saved for the next execution. | |
@inputencoding | The encoding of the input data. One of the following values: | The encoding used to read the input file (for import apps). If the attribute is empty or not defined, the application try to decode the input data with utf-8, if it fails, the application decode the input data with latin1. For a complete list see QTextCodec | |
@inputfilefilter[.lang] | The file filter for the open file dialog Ex.: Text files (*.txt *.csv);;All files (*.*) | This value describes the file filters you want to show in the input file dialog. If you need multiple filters, separate them with ';;' for instance. This tag is localizable. | |
@inputfilepath | The file to read for the input data | If the script has the value fixedfilepath as @inputdatasource, you can define here the path of the file to load. | |
@inputformat | One of the following values: text ac2 | If "text" the filter receive the selected file in inData as a text. If "ac2" the filter receive the selected file in inData as a Banana.Document object. | |
@outputencoding | The encoding of the input data. One of the following values: | The encoding used to write the output file (for export apps). For a complete list see QTextCodec | |
@outputformat | One of the follwing values: tablewithheaders transactions.simple | If the script has an import tasks this value define the format of the returned value. The format transaction.simple contains the transaction as income / expenses. For details of the formats see Import data from a txt file. | |
@pubdate | Required | The publication date in the format YYYY-MM-DD | This publication date is also used for scripts published by Banana.ch to check if newer version exist. |
@publisher | The publisher of the script | ||
@task | Required | One of following values: accounting.payment app.command create.init export.file export.rows export.transactions import.file import.rows import.transactions import.accounts import.categories import.exchangerates import.vatcodes report.general report.customer.invoice report.customer.statement report.customer.reminder | This value define the purpuse of the script, and determine in which dialog or menu the script is visible.
|
@testapp | Path and name of the test(s) app. The path can be relative or absolute, contains wildcards or be a list of files separated by ';'. Default is './test/*.test.js | This can be used if there is a test app and the path to the test app is different to the default path. If the path is not in the script's folder, a confirmation dialog is showed to the user before running the tests. Since Banana 9.0.4 | |
@testappversionmin @testappversionmax | Only for test cases. Minimum and mximum application's version to whitch the test is applicable. | ||
@timeout | The timeout for the script in milliseconds, default is 2000 (2 seconds). If you set -1 the timeout is disabled and the application allow you to abort it though a progress bar. | If the script takes longer than this value to finish, it will be aborted and a message showed. If you have a script with a very long run time, you can increase the timeout or set it to -1. | |
@visibility | always never extensionlinked | Define if the extension will be added to the menu Extensions.
from version 10.0.7 |
1) Default values are listed in bold.
2) Function not yet available
Example:
// @api = 1.0
// @id = ch.banana.apps.example.docfilepath
// @description = Hello world
// @task = app.command
// @doctype = nodocument
// @publisher = Banana.ch SA
// @pubdate = 2015-05-12
// @inputdatasource = none
// @timeout = -1
/**
* Hello world example for Banana Accounting.
*/
function exec(inData) {
Banana.Ui.showInformation("", "Hello World");
if (Banana.document) {
var fileName = Banana.document.info("Base","FileName");
Banana.Ui.showInformation("Current document", fileName);
}
}
Include files, styles and images in your extension
You can create extensions that use external files. In this page we explain how to use an external css file in order to print the report. With this you can avoid to use javascript to set the style attributes.
As with Javascript, CSS is also limited to the attributes listed on the page Banana.Report.ReportStyle.
What do you need:
- The extension script (file .js).
- The CSS stylesheet (file .css).
- Create a package (file .sbaa)
Extension script (file .js)
In your extension javascript file:
- Take the external CSS stylesheet file with getLocalFile(path).
- Read and take the content with read().
- Parse the content in order to load the styles with parse(text).
var textCSS = "";
var file = Banana.IO.getLocalFile("file:script/yourFile.css");
var fileContent = file.read();
if (!file.errorString) {
Banana.IO.openPath(fileContent);
textCSS = fileContent;
} else {
Banana.console.log(file.errorString);
}
// New stylesheet
var stylesheet = Banana.Report.newStyleSheet();
// Parse the CSS text
stylesheet.parse(textCSS);
CSS stylesheet (file .css)
In your css stylesheet file:
- Insert the text following the CSS specification.
@page {
margin-top: 10mm;
margin-bottom: 10mm;
margin-left: 20mm;
margin-right: 10mm;
}
body {
font-family: Helvetica;
font-size: 10pt;
}
table {
font-size: 8px;
width: 100%;
}
table.table td {
border-top-style: solid;
border-top-color: black;
border-top-width: thin;
border-bottom-style: solid;
border-bottom-color: black;
border-bottom-width: thin;
border-left-style: solid;
border-left-color: black;
border-left-width: thin;
border-right-style: solid;
border-right-color: black;
border-right-width: thin;
}
.column1 {
width: 73%;
}
.column2 {
width: 13%;
}
.column3 {
width: 13%;
}
.table-header {
color: #ffffff;
background-color: #337ab7;
font-weight: bold;
text-align: right;
}
.amount-totals {
background-color: #F0F8FF;
text-align: right;
font-weight: bold;
}
Extension package (file .sbaa)
Now generate the Extension Package file (.sbaa) containing:
- The javascript file.
- The CSS file.
To do that:
Create the manifest file (.json) in the same directory of the javascript and css files.
{ "category": "productivity", "country":"switzerland", "countryCode":"ch", "description": "Extension with css", "description.en": "Extension with css", "language":"en", "publisher": "Banana.ch", "title": "Extension with css", "title.en": "Extension with css", "version": "1.0" }
Create the Qrc resource file (.qrc) in the same directory of the javascript and css files.
<!DOCTYPE RCC><RCC version="1.0"> <qresource> <file>yourJavascriptFile.js</file> <file>yourCssFile.css</file> <file>manifest.json</file> </qresource> </RCC>
- Open Banana Accounting
- Drag the .qrc file in Banana Accounting.
- It will ask you if you want to compile the file and will generate a .sbaa file.
Define CSS file in Documents table
You can write the CSS code directly in the documents table and access the data via javascript as shown in the example
/**
* CSS file defined in Documents table.
*/
if (userParam.embedded_css_filename) {
var cssFiles = [];
var documentsTable = Banana.document.table("Documents");
if (documentsTable) {
for (var i = 0; i < documentsTable.rowCount; i++) {
var tRow = documentsTable.row(i);
var id = tRow.value("RowId");
if (id === userParam.embedded_css_filename) {
// The CSS file name entered by user exists on Documents table so it can be used as CSS
textCSS += documentsTable.findRowByValue("RowId",userParam.embedded_css_filename).value("Attachments");
}
}
// Parse the CSS text
repStyleObj.parse(textCSS);
}
}
In this example:
- userParam
An object containing different parameters user can define with the Dialog PropertyEditor. - embedded_css_filename
A the parameter where user enter the file name of the embedded css file saved in the Documents table. The file name is the content of the column Id (e.g. "myStyle.css").
Extension's Settings
Extensions settings allow to customize the Extension, for example:
- Setting for the printing.
- Header of a report that are set once only.
Function settingsDialog()
If you provide customization you should add a function settingDialog().
This function will called:
- When the user click on the Set Parameters on the Manage Extensions dialog.
- For Import extension in the Dialog Import > Button Settings.
- For Report extensions in the Printing Dialog > Button Settings.
Take care that:
- The extension is responsible to manage to load and save the settings.
- Return value.
- null if the user ha clicked on cancel button.
- any other value if the user has changed the settings.
A simple and powerful way to let the user customize the setting is by using the:
Saving the settings
The function settingDialog() should:
- Read the existing setting with the Banana.document.getScriptSettings();
- Request user to enter the information
- Set the modified values with the function Banana.document.setScriptSettings(paramToString);
The JSon text will be saved within the accounting file.
function settingsDialog() {
var param = initParam();
var savedParam = Banana.document.getScriptSettings();
if (savedParam.length > 0) {
param = JSON.parse(savedParam);
}
param = verifyParam(param);
param.isr_bank_name = Banana.Ui.getText('Settings', texts.param_isr_bank_name, param.isr_bank_name);
if (param.isr_bank_name === undefined)
return;
var paramToString = JSON.stringify(param);
Banana.document.setScriptSettings(paramToString);
}
Reading the settings
the function Exec() or any other function should then read the settings using the Banana.document.getScriptSettings().
Before using the setting you should check that they are valid and possibly provide a function that verify and eventually update the settings.
function printDocument(jsonInvoice, repDocObj, repStyleObj) {
var param = initParam();
var savedParam = Banana.document.getScriptSettings();
if (savedParam.length > 0) {
param = JSON.parse(savedParam);
param = verifyParam(param);
}
printInvoice(jsonInvoice, repDocObj, repStyleObj, param);
}
Sharing the setting with other extensions
The function Banana.document.setScriptSettings(paramToString); will save the setting in the accounting file under the Id of the extension.
If you need to share the settings with other extensions, like in case of a series of packaged extensions you should use the Banana.document.setScriptSettings((id, value).
And to read the setting use Banana.document.getScriptSettings((id).
For more information siee Banana.document.
Example:
// Save script settings
var paramString = JSON.stringify(param);
var value = Banana.document.setScriptSettings('ch.banana.vat', paramString);
Translations and multi language extension's
With BananaPlus you can provide translations to your extensions in a very simple and straightforward way. After you make the extension ready to be translated, adding a new language is as simple as adding a file to the package, without the need to change the source code.
Make an extension translatable
To make an extension translatable you need to:
- Install CMake, Visual Studio code and setup your development environment
- Mark strings for translation with the functions qsTr() or QT_TRANSLATE_NOOP() in *.js and *.qml files;
- Add a CMakeLists.txt file to your project and modify it;
- Translate strings with QtLinguist;
- Add the translations files *.qm in the resource file *.qrc;
- Build and deploy the extension's package;
You find a working complete example with the code described in this page on our GitHub Repository at: Sample extension for string translations
Limitations:
- JS and QML files can not contains a dot '.' in the file name: for example my.extention.js is no valid, but my_extension.js is valid;
- JS and QML files can not be renamed via the property Alias in resource files (*.qrc), for example from my.extention.js to my_extention.js or main.js;
- Translations files (*.qm) have to be found under the subfolder /translations;
Setup development environment
Follow the steps on the page setup your development environment.
Mark strings for translation
Strings to be translated have to be marked in .js or .qml files using the function qsTr() or QT_TRANSLATE_NOOP().
Strings marked for translations are extracted from .js and .qml files and inserted in translation's files *.ts. Translations files are created and compiled with Qt Creator and the translations done with Qt Linguist.
Function qsTR()
With the function qsTr() you mark strings to be translated in the application's language. Those are usually texts showed in the user interface (dialogs, messages, ...).
Example:
var translatedText = qsTr("Account card");
// The variabile translatedText contains the translated string "Account card"
// in the application's language
Function QT_TRANSLATE_NOOP()
With the function QT_TRANSLATE_NOOP() you mark strings to be translated in the document language. Those are usually texts printed in reports or invoices.
Those translation are retrieved through the API Banana.Translations.
Example:
// Exemple for function QT_TRANSLATE_NOOP
var myReportTexts = {};
myReportTexts.account_card = QT_TRANSLATE_NOOP("MyReport", "Account card"); // Mark text for translation
// NB: The variable myReportTexts.account_card contains the source code string "Account card"
// You need a Banana.Translator object to translate it in the desired language
// Get translator for the document's language
var documentLanguage = "en"; //default
if (Banana.document) {
documentLanguage = Banana.document.locale.substring(0,2);
}
var docTranslator = Banana.Translations.getTranslator(documentLanguage, "MyReport");
// Translate to the document's language
var myReportTranslatedText = docTranslator.tr(myReportTexts.account_card);
Add a CMake project file
A CMakeLists.txt project file is needed to extract all strings marked for translation from .js and .qml files and build the .sbaa extension's package.
Below is a template of a project file for translating an extension, you can copy it and modify it:
- copy the example and save it as CMakeLists.txt in your project folder;
- rename the project name translations_project to your desired name;
- renane the extension id ch.banana.translations to your desired id;
- add the desired languages under the variable translations_files;
- add source files as needed to the main target;
cmake_minimum_required(VERSION 3.16)
project(translations_project) # <!-- CHANGE THE PROJECT'S NAME
set(EXTENSION_ID "ch.banana.translations") # <!-- CHANGE THE EXTENSION'S ID
# CMake options
# Create a file .vscode/settings.json with the following content to set the options,
# adapt the path to your environment
# {
# "cmake.configureSettings": {
# "BAN_QT_RCC": "C:\users\user_name\AppData\Local\Programs\BananaPlusDev\rcc.exe",
# "BAN_EXE_PATH": "C:\users\user_name\AppData\Local\Programs\BananaPlusDev\BananaPlusDev.exe",
# "BAN_QT_LUPDATE": "C:\Qt\6.5.2\macos\bin\lupdate",
# "BAN_QT_LRELEASE": "C:\Qt\6.5.2\macos\bin\lrelease"
# }
# }
set(BAN_QT_RCC $ENV{BAN_QT_RCC} CACHE FILEPATH "Path to Qt rcc executable")
set(BAN_EXE_PATH $ENV{BAN_EXE_PATH} CACHE FILEPATH "Path to BananaPlus executable, used to run tests")
set(BAN_QT_LUPDATE $ENV{BAN_QT_LUPDATE} CACHE FILEPATH "Path to Qt lupdate executable")
set(BAN_QT_LRELEASE $ENV{BAN_QT_LRELEASE} CACHE FILEPATH "Path to Qt lrelease executable")
# This target is used to build the extension to a sbaa package
add_custom_target(translations_project ALL # <!-- CHANGE THE PROJECT'S NAME
COMMAND ${BAN_QT_RCC} -o ${EXTENSION_ID}.sbaa --binary ${EXTENSION_ID}.qrc
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
SOURCES ${EXTENSION_ID}.qrc
)
add_dependencies(${PROJECT_NAME} lrelease) #build .qm files when building the sbaa package
# The variable translations_files contains the list of translations files
set(translations_files
translations/translations_de.ts
translations/translations_it.ts
translations/translations_fr.ts
#translations/translations_xx.ts # <!-- ADD LANGUAGES AS NEEDED
)
# The target lupdate is used to update *.ts translations files
set(lupdate_commands)
foreach(tr_file ${translations_files})
list(APPEND lupdate_commands
COMMAND ${BAN_QT_LUPDATE} ${EXTENSION_ID}.qrc -ts ${tr_file})
endforeach()
add_custom_target(lupdate
${lupdate_commands}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
# The target lrelease is used to compile *.ts files to *.qm files
set(lrelease_commands)
set(lrelease_files)
string(REPLACE ".ts" "" lrelease_files "${translations_files}") #remove file extension
foreach(tr_file ${lrelease_files})
list(APPEND lrelease_commands
COMMAND ${BAN_QT_LRELEASE} ${tr_file}.ts -qm ${tr_file}.qm)
endforeach()
add_custom_target(lrelease
${lrelease_commands}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
Set Cmake options
Create the file .vscode/settings.json and set the following options:
- BAN_QT_RCC: path to the Qt rcc tool. The rcc Tool is used to compile the extension to a sbaa package.
- BAN_EXE_ PATH: path to the BananaPlus executable. The BananaPlus executable is used to run the tests defined in the project.
- BAN_QT_LUPDATE: path to the Qt lupdate tool. The lupdate tool is used to update the translations, it will search for text to translate in the code and update the content of *.ts files. If you don't have translations, this options is not necessary.
- BAN_QT_RELEASE: path to the Qt lrealease tool. The lrelease tool is used to compile the translations, the compiled translation are integrated in the package.If you don't have translations, this options is not necessary.
{
"cmake.configureSettings": {
"BAN_QT_RCC": "C:\Programms\BananaPlusDev\rcc.exe",
"BAN_EXE_PATH": "C:\Programms\BananaPlusDev\BananaPlusDev.exe",
"BAN_QT_LUPDATE": "C:\Qt\6.5.2\macos\bin\lupdate",
"BAN_QT_LRELEASE": "C:\Qt\6.5.2\macos\bin\lrelease"
}
}
Translate strings
- Create a folder /translations in your repository;
- Open the CMakeLists.txt project file with QtCreator;
- Build in QtCreator the target lupdate (figure 1);
- Open the generated translations/*.ts files with QtLinguist and enter the translations (figure 2)
Keep the generated translations/*.ts files in the repository, as they are needed for updating the translations after one or more strings are changed or modified in the code.
Add translations to resource file
Add the *.qm files to the resource *.qrc file.
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
<file>manifest.json</file>
<file>translations/translations_de.qm</file>
<file>translations/translations_fr.qm</file>
<file>translations/translations_it.qm</file>
</qresource>
</RCC>
Build and deploy
- Build the extension;
- The translations files *.qm are automatically released and included in the package;
- Copy the generated *.sbaa file to the destination folder;
- Add the extension in Banana AccountingPlus (see installing an'extension);
- Test it;
Implementation details
Loading of translation's files
Translations are automatically loaded by the application.
Translations are provided as *.qm binary files located in the extension's subfolder /translations.
sample_extention.js // extension translations/ translations_de.qm. // translation's files translations_it_CH.qm translations_it.qm ...
Translations files have the following naming convention:
translations_lang[_country].qm
Before the execution of the extension the application loads the translation corresponding to the application's language and country. For example if the application has the locale fr_FR the application looks for the following files, the first file found is taken.
translations_fr_FR.qm translations_fr.qm​​​