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.

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
  • 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
    • 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.

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 
ValueDescriptionExample
developerThe developer of the app.ch.banana
countryThe 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
typeThe 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 nameRequiredValue 1)Description
@apiRequired

The required API version.

Available API versions:
1.0

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]RequiredThe 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.
The commands of the extension will the be added to the menu Extensions only if the property mach the one present the file properties keywords.

  • The property can be any text (ex.: "datev", "realestate", ...).
  • Multiple properties can be defined with a ';' as separator (ex.: "datev;skr03").

It works in combination of the @docproperties and @visibility

@doctypeRequirednodocument
*
XXX.*
XXX.YYY
!XXX.YYY
...

Define the type of document the script is written for.
The commands of the extension will the be added to the menu Extensions only if the document is of the specified type.
It works in combination of the @doctype and @visibility

nodocument = doesn't require an open document, the add-on is always visible
* = for any type of document, always visible if a document is open
*.*  = for any type of document, always visible if a document is open


100.* = Double entry accounting
100.100 = Double  entry without VAT and without foreign currencies
100.110 = Double  entry with VAT
100.120 = Double  entry with foreign currencies
100.130 = Double  entry with VAT and foreign currencies


110.* = for Income & Expenses accounting
130.* = for Cash Books

300.* = All productivities App
400.100 = Simple Table
400.100 = Simple Table
400.120 = Addresses and Labels
400.140 = Library and collections
400.150 = Fixed Assets Register
400.300 = Tutorial maker
400.400= Estimates and Invoices files
400.500 = Inventory
400.600 = Timesheet

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
100.120;100.130 = for double entry with foreign currencies
100.*;110.*;130.* = for all accounting files
!130.* = for any files except cash books

@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.
@idRequiredAn identification of the scriptIt 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:

  1. In the same directory as the file that contains the @include attribute.
  2. In the directories of the currently opened include files, in the reverse order in which they were opened. 

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:
latin1
utf-8
iso 8859-1 to 10
...

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 dataIf 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:
latin1
utf-8
iso 8859-1 to 10

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.
@pubdateRequiredThe publication date in the format YYYY-MM-DDThis publication date is also used for scripts published by Banana.ch to check if newer version exist.
@publisher The publisher of the script 
@taskRequiredOne 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
Since Banana 10.1.13: list of paths and wildcards

@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.
It works in combination of the @docproperties and @doctype

  • always = the commands of the extension will be added to the menu Extensions
  • never = the commands of the extension won't appear in the menu Extensions
  • extensionlinked= the commands of the extension will be added to the menu Extensions only if the script is linked in the file and accounting properties

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:



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:

  1. Read the existing setting with the Banana.document.getScriptSettings();
  2. Request user to enter the information
  3. 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:

  1. Install CMake, Visual Studio code and setup your development environment
  2. Mark strings for translation with the functions qsTr() or QT_TRANSLATE_NOOP() in *.js and *.qml files;
  3. Add a CMakeLists.txt file to your project and modify it;
  4. Translate strings with QtLinguist;
  5. Add the translations files *.qm in the resource file *.qrc;
  6. 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

  1. Create a folder /translations in your repository;
  2. Open the CMakeLists.txt project file with QtCreator;
  3. Build in QtCreator the target lupdate (figure 1);
  4. 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.

Update translations

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

  1. Build the extension;
  2. The translations files *.qm are automatically released and included in the package;
  3. Copy the generated *.sbaa file to the destination folder;
  4. Add the extension in Banana AccountingPlus (see installing an'extension);
  5. Test it;

Build the extension

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​​​