/**
* @file trans.js The core class of Translator++
* @author Dreamsavior
* File version: 2024-04-03 14:22:34.304
*/
/**
* Executed each time trans file is loaded or initialized
* @event Trans#transLoaded
*/
window.fs = require('graceful-fs');
window.afs = require('await-fs');
window.nwPath = require('path');
window.spawn = require('child_process').spawn;
window.debounce = require("debounce");
//================================================================
//
// COMMON FUNCTION
//
//================================================================
/**
* Insert an array into another array at some index
* @global
* @param {Array} array - Source array
* @param {Number} index - Index to insert at
* @param {Array} arrayToInsert - Array to insert
* @returns {Array} - Merged array
*/
window.insertArrayAt = function(array, index, arrayToInsert) {
Array.prototype.splice.apply(array, [index, 0].concat(arrayToInsert));
return array;
}
global.common ||= {}
common.arrayExchange = function(arr, fromIndex, toIndex) {
// exchange an array value by index (single)
var element = arr[fromIndex];
arr.splice(fromIndex, 1);
arr.splice(toIndex, 0, element);
}
common.arrayExchangeBatch = function(input, fromIndex, toIndex) {
// exchange an array value by indexes(array)
if (Array.isArray(fromIndex) == false) {
common.arrayExchange(input, fromIndex, toIndex);
}
for (var i=fromIndex.length-1; i>=0; i--) {
common.arrayExchange(input, fromIndex[i], toIndex);
}
return input;
}
common.arrayMove = function(array, fromIndex, to) {
// move an array index to new index
if(Array.isArray(array) == false) return array;
if( to === fromIndex ) return array;
var target = array[fromIndex];
var increment = to < fromIndex ? -1 : 1;
for(var k = fromIndex; k != to; k += increment){
array[k] = array[k + increment];
}
array[to] = target;
return array;
}
common.arrayMoveBatch = function(array, fromIndex, to) {
if (Array.isArray(fromIndex) == false) {
return common.arrayMove(array, fromIndex, to);
}
var n=0;
for (var i=fromIndex.length-1; i>=0; i--) {
array = common.arrayMove(array, fromIndex[i]+n, to);
n++;
}
return array;
}
common.escapeSelector = function(string) {
string = string||"";
if (typeof string !== 'string') return false;
//return string.replace( /(:|\.|\[|\]|,|=|@)/g, "\\$1" );
//return string.replace( /(:|\.|\[|\]|,|=|@|\s|\(|\))/g, "\\$1" );
//return '"'+string+'"';
return ('"'+CSS.escape(string)+'"')
}
common.arrayInsert =function(thisArray, index, item ) {
thisArray.splice( index, 0, item );
return thisArray;
};
common.batchArrayInsert = function(thisArray, index, item ) {
for (var i=0; i<thisArray.length; i++) {
this.arrayInsert(thisArray[i], index, item);
}
return thisArray;
};
var FileLoader = function() {
this.handler = {};
}
FileLoader.prototype.add = function(extension, handler) {
// handler is function with arguments : filepath
this.handler[extension] = handler;
}
FileLoader.prototype.open = function(extension, handler) {
// handler is function with arguments : filepath
//this.handler['extension'] = handler;
}
window.FileLoader = FileLoader;
//================================================================
//
// T R A N S C L A S S
//
//================================================================
/**
* @class
* @classdesc
* The core class of Translator++
* Handle basic logic of Translator++ application
* This class will have one instance for each window. Which is `window.trans`
*/
class Trans extends require('www/js/BasicEventHandler.js') {
constructor() {
super($(document));
this.init();
}
}
/**
* Maximum column allowed in the grid
*/
Trans.maxCols = 15;
/**
* Handle cell level information
* @class
* @since 4.4.4
* @classdesc
* Manage cell's additional informations
*/
Trans.CellInfo = function() {
}
/**
* Get all cell information
* @param {String} file - The file ID
* @returns {Array} - The cell information
*/
Trans.CellInfo.prototype.getAll = function(file) {
if (!file) return;
if (!trans?.project?.files?.[file]) return;
var obj = trans.getObjectById(file);
if (!obj.cellInfo) obj.cellInfo = [];
return obj.cellInfo;
}
/**
* Get row information
* @param {String} file - The file ID
* @param {Number} row - The row number
* @returns {Array} - The row information
*/
Trans.CellInfo.prototype.getRow = function(file, row) {
var cellInf = this.getAll(file);
if (!cellInf) return;
return cellInf[row];
}
/**
* Get cell information
* @param {String} file - The file ID
* @param {Number} row - The row number
* @param {Number} col - The column number
* @returns {Object} - The cell information
*/
Trans.CellInfo.prototype.getCell = function(file, row, col) {
var rowInf = this.getRow(file, row);
if (!rowInf) return;
return rowInf[col];
}
/**
* Get the best translation cell information
* @param {String} file - The file ID
* @param {Number} row - The row number
* @param {String} key - The key to get
* @returns {Object} - The cell information
*/
Trans.CellInfo.prototype.getBestCellInfo = function(file, row, key) {
var data = trans.getData(file);
var col = trans.getTranslationColFromRow(data[row]);
var cellInfo = this.getCell(file, row, col);
if (!cellInfo) return cellInfo;
if (key) return cellInfo[key];
return cellInfo;
}
/**
* Get configuration of a cell
* @param {String} key - The key to get
* @param {String} file - The file ID
* @param {Number} row - The row number
* @param {Number} col - The column number
* @returns {*} - The value of the key
*/
Trans.CellInfo.prototype.get = function(key, file, row, col) {
var cellInf = this.getCell(file, row, col);
if (!cellInf) return;
return cellInf[key];
}
/**
* Set cell information
* @param {String} key - The key to set
* @param {*} value - The value to set
* @param {String} file - The file ID
* @param {Number} row - The row number
* @param {Number} col - The column number
* @returns {Boolean} - True if success
*/
Trans.CellInfo.prototype.set = function(key, value, file, row, col) {
//console.log("Setting cell info with options:", arguments);
var cellInf = this.getAll(file);
cellInf[row] = cellInf[row] || [];
cellInf[row][col] = cellInf[row][col] || {};
cellInf[row][col][key] = value;
return true;
}
/**
* Delete cell information
* @param {String} key - The key to delete
* @param {String} file - The file ID
* @param {Number} row - The row number
* @param {Number} col - The column number
* @returns {Boolean} - True if success
*/
Trans.CellInfo.prototype.delete = function(key, file, row, col) {
//console.log("Setting cell info with options:", arguments);
var cellInf = this.getAll(file);
cellInf[row] = cellInf[row] || [];
cellInf[row][col] = cellInf[row][col] || {};
delete cellInf[row][col][key]
return true;
}
/**
* Delete row information
* @param {String} file - The file ID
* @param {Number} row - The row number
* @returns {Boolean} - True if success
*/
Trans.CellInfo.prototype.deleteRow = function(file, row) {
var cellInf = this.getAll(file)
if (empty(cellInf)) return;
if (Array.isArray(cellInf)) {
cellInf.splice(row, 1);
} else {
delete cellInf[row];
}
}
// Trans.CellInfo.prototype.moveColumn = function(file, from, to) {
// var cellInf = this.getAll(file)
// if (empty(cellInf)) return;
// if (Array.isArray(cellInf)) {
// cellInf.splice(row, 1);
// } else {
// delete cellInf[row];
// }
// return true;
// }
Trans.CellInfo.prototype.deleteCell = function(file, rowId, cellId) {
var thisRow = this.getRow(file, rowId);
if (!Array.isArray(thisRow)) return;
thisRow.splice(cellId, 1);
return true;
}
Trans.CellInfo.prototype.moveCell = function(file, rowId, from, to) {
var thisRow = this.getRow(file, rowId);
if (!Array.isArray(thisRow)) return;
common.arrayMoveBatch(thisRow, from, to);
return true;
}
/**
* Create a new event with JQuery eventing convenience
* Equal to `$(document).on()`
* @param {String} evt - Event name
* @param {Function} fn - Function to trigger
* @since 4.3.20
* trans.on('transLoaded', (e, opt)=> {
* // do something
* })
*/
/**
* Removes an event
* Equal to `$(document).off()`
* @param {String} evt - Event name
* @param {Function} fn - Function to trigger
* @since 4.3.20
* @example
* trans.off('transLoaded', (e, opt)=> {
* // do something
* })
*/
/**
* Run the event once
* Trigger an event and immediately removes it
* Equal to `$(document).one()`
* @param {String} evt - Event name
* @param {Function} fn - Function to trigger
* @since 4.3.20
*/
/**
* Trigger an event
* Equal to `$(document).trigger()`
* @param {String} evt - Event name
* @param {Function} fn - Function to trigger
* @since 4.3.20
*/
/**
* Initialization of the Trans object
*/
Trans.prototype.init = function() {
this.config ={
loadRomaji :true,
maxRequestLength:3000,
autoSaveEvery :600,
batchDelay :5000,
rpgTransFormat :true,
autoTranslate :false
},
/**
* Index of key column. The column to store original texts.
* @default 0
*/
this.keyColumn = 0;
this.isFreeEditing = false;
this.gameTitle =""
this.gameEngine =""
this.projectId =""
this.indexIds ={}
this.fileListLoaded =false
this.isLoadingFileList =false
this.unsavedChange =false
//files:{},
this.gameFolder = ''
this.currentFile = '' //current .trans file
this.skipElement =['note', 'Comment', 'Script']
/**
* The current project
*/
this.project =undefined
this.timers ={}
/**
* The current active data on the grid
*/
this.data =[];
this.colHeaders =[t('Original Text'), t('Initial'), t('Machine translation'), t('Better translation'), t('Best translation')];
this.onFileNavLoaded = function() {}
this.onFileNavUnloaded = function() {}
this.validateKey = function(value, callback) {
console.log("key validator", value);
if (value=='' || value==null) {
callback(false);
} else {
callback(true);
}
}
this.columns = [{
readOnly: false,
validator: this.validateKey,
width: 150,
//trimWhitespace: false
},
{
readOnly: false,
//trimWhitespace: false
},
{
readOnly: false,
//trimWhitespace: false
},
{
readOnly: false,
//trimWhitespace: false
},
{
readOnly: false,
//trimWhitespace: false
}
];
this.default = {};
this.default.columns = [{
readOnly: false,
validator: this.validateKey,
//trimWhitespace: false
},
{
readOnly: false,
//trimWhitespace: false
},
{
readOnly: false,
//trimWhitespace: false
},
{
readOnly: false,
//trimWhitespace: false
},
{
readOnly: false,
//trimWhitespace: false
}
]
/**
* Whether trans is currently handling a project or not.
*/
this.inProject = false;
this.default.colHeaders =[t('Original Text'), t('Initial'), t('Machine translation'), t('Better translation'), t('Best translation')];
}
Trans.prototype.isTrans = function(obj) {
if (!obj) return false;
if (obj.constructor.name!=="Object" && obj.constructor.name!=="Trans") return false;
if (!obj?.project?.files) return false;
return true;
}
/**
* Get project's option
* Option are user editable configuration
* @param {String} key
*/
Trans.prototype.getOption = function(key) {
if (!trans.project) return;
trans.project.options ||= {};
// override default behavior
if (typeof trans.project.options.gridInfo == "undefined") {
trans.project.options.gridInfo = {
isRuleActive : true,
enableTrail : true,
viewTrail : false,
viewOrganicCellMarker:true,
rowHeaderInfo : false
}
}
return trans.project.options[key];
}
Trans.prototype.setOption = function(key, value) {
if (!trans.project) return;
trans.project.options ||= {};
trans.project.options[key] = value;
}
/**
* Get project configuration
* Config are system defined configuration. Should not editable by user.
* @param {String} key - The configuration key
* @returns {*} - The configuration related to the Key
*/
Trans.prototype.getConfig = function(key) {
trans.project.config = trans.project.config || {};
if (typeof key == "string") return trans.project.config[key];
if (Array.isArray(key)) {
var result = trans.project.config;
try {
for (var i=0; i<key.length; i++) {
result = result[key[i]];
}
return result;
} catch (e) {
return;
}
}
return;
}
/**
* Set key-value pair of configuration
* @param {(String|String[])} key - The key
* @param {*} value - The value
* @returns {Boolean} - True if success
*/
Trans.prototype.setConfig = function(key, value) {
trans.project.config = trans.project.config || {};
if (typeof key == "string") {
trans.project.config[key] = value;
return true;
}
if (Array.isArray(key)) {
var result = trans.project.config;
try {
for (var i=0; i<key.length-1; i++) {
result[key[i]] = result[key[i]] || {};
result = result[key[i]];
}
result[key[i]] = value;
return true;
} catch (e) {
return;
}
}
return;
}
/**
* Check if a project is currently opened
* @returns {Boolean} - Returns true if trans is in a project
* @since 6.1.18
*/
Trans.prototype.isInProject = function() {
return this.inProject;
}
/**
* Get the template path
* @returns {String} - The path to the template file
*/
Trans.prototype.getTemplatePath = function() {
var templatePath = sys.config.templatePath||nw.App.manifest.localConfig.defaultTemplate
fs = fs||require('fs')
try {
if (fs.existsSync(templatePath)) {
return templatePath;
}
} catch (err) {
return nwPath.join(__dirname, templatePath);
}
}
/**
* merge reference into files object in transObj.project
* @function
* @param {object} transObj instance of trans object
* @returns {object} instance of trans object
*/
Trans.prototype.mergeReference = function(transObj) {
console.log("Merging reference");
transObj = transObj||{};
transObj.project = transObj.project||{};
transObj.project.references = transObj.project.references||{};
transObj.project.files = transObj.project.files||{};
for (let ref in transObj.project.references) {
console.log("assigning : ", ref);
transObj.project.files[ref] = this.project.references[ref];
}
return transObj;
}
/**
* determine wether the pathname is supported file formats
* @function
* @param {string} pathName
* @returns {boolean} True if file is supported, otherwise false.
*/
Trans.prototype.isFileSupported = function(pathName) {
if (typeof pathName !== 'string') return false;
var ext = common.getFileExtension(pathName);
if (sys.supportedExtension.includes(ext)) return true;
return false;
}
/**
* do some action depending on the file type
* @function
* @param {string} file path to the file
*/
Trans.prototype.openFile = function(file) {
const spawn = require("child_process").spawn;
if(this.isFileSupported(file) == false) return false;
var ext = common.getFileExtension(file);
if (typeof (this.fileLoader.handler[ext]) !== 'function') return false;
if (this.fileListLoaded == false) {
// load in this window
ui.introWindowClose();
this.fileLoader.handler[ext].apply(this, [file]);
} else {
// load on new window
//var spawn = spawn || require('child_process').spawn;
spawn(nw.process.execPath, [file], {
detached :true
});
}
}
/**
* Initialize the project
* @function
* @todo Implement this function
*/
Trans.prototype.initProject = function() {
if (typeof this.project !== 'undefined') {
console.log("project is already been initialized! skipping!");
return false
}
// this function is not done yet
}
/**
* Close the current project
* @function
*
*/
Trans.prototype.closeProject = function() {
if (typeof this.project == 'undefined') {
return false
}
if (typeof this.grid.destroy() == 'function') this.grid.destroy();
this.unInitFileNav();
this.unInitLocalStorage();
this.project = {};
this.init();
ui.tableCornerHideLoading(true);
ui.closeAllChildWindow();
ui.ribbonMenu.clear();
ui.clearActiveCellInfo();
ui.clearPathInfo();
ui.setWindowTitle("");
trans.clearFooter();
this.initTable();
this.gridIsModified(false);
}
/**
* Generates new dictionary table
* @function
* @returns {object} references object (trans.project.references)
*/
Trans.prototype.generateNewDictionaryTable = function() {
var thisID = "Common Reference";
if (typeof this.project.files[thisID] !== 'undefined') return this.project.files[thisID];
if (typeof this.project.references == 'object') {
console.log("trans.project.reference is an object");
for (var fileId in this.project.references) {
this.project.files[fileId] = this.project.references[fileId];
}
} else {
console.log("trans.project.reference is not an object");
var templatePath = this.getTemplatePath()
var templateObj = this.loadJSONSync(templatePath);
console.log("template obj : ", templateObj);
if (Boolean(templateObj) !== false) {
this.project.references = templateObj.project.references;
console.log("assigning reference : ", this.project.references);
}
this.mergeReference(trans);
}
return this.project.references;
}
/**
* Initialize a new project
* @function
* @param {object} options
* force
* selectedFile {array}
*/
Trans.prototype.createProject = function(options) {
console.log("running trans.createProject");
if (this.isLoadingFileList) return false;
if (this.gameFolder == "") return false;
var trans = this;
options = options||{};
options.force = options.force||"";
options.selectedFile = options.selectedFile||"";
options.onAfterLoading = options.onAfterLoading||function(responseData, event) {}; // eslint-disable-line
options.options = options.options||{};
this.isLoadingFileList = true;
ui.showLoading();
var thisArgs = {
gameFolder :this.gameFolder,
selectedFile :options.selectedFile,
gameEngine :this.gameEngine,
gameTitle :this.gameTitle,
projectId :this.projectId,
skipElement :this.skipElement,
indexOriginal :0,
indexTranslation:1,
force :options.force,
rpgTransFormat :trans.config.rpgTransFormat,
options :options.options
}
console.log("Sending this args to loadGameInfo.php : ", thisArgs);
php.spawn("loadGameInfo.php", {
args:thisArgs,
onData: function(buffer) {
ui.loadingProgress(t("Loading"), buffer, {consoleOnly:true, mode:'consoleOutput'});
},
onError:function(buffer) {
ui.loadingProgress(t("Loading"), buffer, {consoleOnly:true, mode:'consoleOutput', classStr:'stderr'});
},
onDone : function(data) {
console.log("onDone event defined from trans.js");
if (common.isJSON(data)) {
trans.project = data;
trans.currentFile = "";
trans.gameTitle = data.gameTitle;
trans.gameFolder = data.loc;
trans.projectId = data.projectId;
trans.gameEngine = data.gameEngine;
trans.editorName = "Translator++";
trans.editorVersion = nw.App.manifest.version;
trans.generateHeader();
trans.sanitize();
//trans.removeAllDuplicates();
trans.dataPadding();
sys.updateLastOpenedProject();
trans.autoSave();
//JSON.parse(data);
console.log(data);
ui.setStatusBar(1, trans.gameTitle);
/**
* Executed when a project is created
* @event Trans#projectCreated
* @param {Trans} trans - Instance of the current trans data
* @param {Object} data
*/
trans.trigger("projectCreated", trans, data);
if (typeof options.onAfterLoading == 'function') {
options.onAfterLoading.call(trans, data, this);
}
trans.fileListLoaded = true;
trans.isLoadingFileList = false;
ui.showCloseButton();
} else {
// try to find link from console response
var loadedData = $("<div>"+data+"</div>");
var initPath = loadedData.find("#initialDataPath").attr("data-path");
console.log("found initPath : "+initPath);
//var initData = loadedData.find("#initialData").text();
//console.log(JSON.parse(initData));
if (Boolean(initPath) == false) {
ui.loadingProgress("Error!", "Can not successfully parse your game! Read the documentation here: https://dreamsavior.net/?p=1311",
{consoleOnly:true, mode:'consoleOutput'});
ui.showCloseButton();
trans.fileListLoaded = false;
trans.isLoadingFileList = false;
return;
}
try {
fs.readFile(initPath, function (err, rawData) {
if (err) {
console.log("error opening file : "+initPath);
ui.loadingProgress("Error!", "Error opening file : "+initPath,
{consoleOnly:true, mode:'consoleOutput'});
ui.showCloseButton();
trans.fileListLoaded = false;
trans.isLoadingFileList = false;
throw err;
} else {
var strData = rawData.toString();
var data = {};
try {
data = JSON.parse(strData);
} catch (err) {
ui.loadingProgress(t("Error!"), t("Error processing init file : ")+initPath+"\n"+err,
{consoleOnly:true, mode:'consoleOutput'});
ui.showCloseButton();
trans.isLoadingFileList = false;
return false;
}
if (Boolean(data['files']) == false) {
ui.loadingProgress(t("Error!"), t("Error! File list not found in init file at : ")+initPath,
{consoleOnly:true, mode:'consoleOutput'});
ui.loadingProgress(t("Error!"), t("This means that your game was not successfully parsed. Please visit https://dreamsavior.net/?p=1311 for possible solution for this issue."),
{consoleOnly:true, mode:'consoleOutput'});
ui.showCloseButton();
trans.isLoadingFileList = false;
return false;
}
trans.project = data;
trans.currentFile = "";
trans.gameTitle = data.gameTitle;
trans.gameFolder = data.loc;
trans.projectId = data.projectId;
trans.gameEngine = data.gameEngine;
trans.editorName = "Translator++";
trans.editorVersion = nw.App.manifest.version;
trans.generateHeader();
trans.sanitize();
//trans.removeAllDuplicates();
trans.dataPadding();
sys.updateLastOpenedProject();
trans.autoSave();
//JSON.parse(data);
console.log(data);
ui.setStatusBar(1, trans.gameTitle);
trans.trigger("projectCreated", trans, data);
if (typeof options.onAfterLoading == 'function') {
options.onAfterLoading.call(trans, data, this);
}
trans.fileListLoaded = true;
trans.isLoadingFileList = false;
ui.loadingProgress(t("Done!"), t("All done!"), {consoleOnly:false, mode:'consoleOutput'});
ui.loadingEnd();
}
});
} catch (error) {
console.log("error opening file : "+initPath);
ui.loadingProgress(t("Error!"), t("Error opening file : ")+initPath, {consoleOnly:false, mode:'consoleOutput'});
ui.loadingProgress(t("Error!"), error, {consoleOnly:true, mode:'consoleOutput'});
ui.loadingEnd();
trans.fileListLoaded = false;
trans.isLoadingFileList = false;
}
var $tmpPath = loadedData.find("#tmpPath");
if ($tmpPath.length > 0) {
ui.showOpenCacheButton($tmpPath.text());
}
}
}
});
}
Trans.prototype.procedureCreateProject =function(gamePath, options) {
console.log("running trans.procedureCreateProject");
console.log(arguments);
if (typeof gamePath == 'undefined') return false;
options = options||{};
options.selectedFile = options.selectedFile||"";
options.force = options.force||"";
options.projectInfo = options.projectInfo||{
id :"",
title :""
}
options.options = options.options||{};
options.gameEngine = options.gameEngine||"";
//console.log(options);
this.closeProject();
this.projectId = options.projectInfo.id;
this.gameTitle = options.projectInfo.title;
this.gameFolder = gamePath;
this.gameEngine = options.gameEngine;
//console.log("=======================");
//console.log("Running trans.initFileNav() with current trans : ");
//console.log(trans);
// close newProject dialog box if any
ui.newProjectDialog.close();
this.createProject({
selectedFile:options.selectedFile,
force:options.force,
onAfterLoading:async function(rawData) { // eslint-disable-line
this.drawFileSelector();
// record the project creation options
this.project.options = this.project.options || {};
this.project.options.init = options.options || {};
if (engines.hasHandler('onAfterCreateProject')) {
await common.wait(100);
await engines.handler('onAfterCreateProject').call(this, gamePath, options);
}
},
options:options.options
});
}
Trans.prototype.getRow = function(fileData, keyword) {
// locate a row number of a key
// returns row number
if (typeof keyword !== 'string') return undefined;
console.log("Get row", arguments);
if (fileData.indexIsBuilt == false) {
console.log("building index for the first time");
this.buildIndexFromData(fileData);
}
fileData.indexIds = fileData.indexIds || {};
console.log("getRow result is : ", fileData.indexIds[keyword]);
return fileData.indexIds[keyword];
}
/**
* update current trans with new jsonData
* @param {Trans} jsonData - jsonData must contains jsonData.project.files
* @param {*} options
* @returns {Trans} - An updated trans data
*/
Trans.prototype.updateProject = function(jsonData, options) {
/* update current trans with new jsonData
jsonData must contains :
jsonData.project.files
*/
if (typeof jsonData == 'string') jsonData = JSON.parse(jsonData);
options = options||{};
options.onAfterLoading = options.onAfterLoading||function(type, responseData, event) {};
options.onSuccess = options.onSuccess||function(responseData, event) {};
options.onFailed = options.onFailed||function(responseData, event) {};
options.filePath = options.filePath||"";
options.purgeNonExistingData ||= false;
jsonData.project = jsonData.project || {};
jsonData.project.files = jsonData.project.files || {};
const projectCopy = jsonData;
const oldProject = this.getSaveData();
const oldFiles = oldProject.project.files||{};
console.log("Base data", jsonData);
console.log("Preserved current files", oldFiles);
projectCopy.project.files = jsonData.project.files;
for (let file in jsonData.project.files) {
var thisFile = projectCopy.project.files[file];
if (!oldFiles[file]) continue;
if (!oldFiles[file].data) continue;
if (oldFiles[file].data.length < 1) continue;
console.log("%cprocessing file", "color:aqua", file);
if (options.purgeNonExistingData) {
// new trans as pointer, good if we don't want to preserve existing record
for (let rowId=0; rowId < thisFile.data.length; rowId++ ) {
let key = thisFile.data[rowId][0];
var oldFileRow = this.getRow(oldFiles[file], key);
console.log("%cKey", "color:aqua", key);
console.log("%coldFileRow", "color:aqua", oldFileRow);
if (typeof oldFileRow == 'undefined') continue;
if (!oldFiles[file].data[oldFileRow]) continue;
for (let x=1; x<oldFiles[file].data[oldFileRow].length; x++) {
//console.log("%c--Assigning ", "color:aqua", oldFiles[file].data[oldFileRow][x], "to col", x);
thisFile.data[rowId][x] = oldFiles[file].data[oldFileRow][x];
}
}
} else {
// preserve the old record that not exist in the newly updated data
let thisOldFile = oldFiles[file];
let contextCounter = 0;
for (let rowId=0; rowId < thisOldFile.data.length; rowId++ ) {
if (!thisOldFile.data[rowId]?.length) continue;
let key = thisOldFile.data[rowId][0];
if (!key) continue;
var fileRow = this.getRow(thisFile, key);
// console.log("%cKey", "color:aqua", key);
// console.log("%coldFileRow", "color:aqua", fileRow);
if (typeof fileRow == 'undefined') {
//console.log("%cHandling non existing record", "color:aqua", thisOldFile.data[rowId]);
let newLen = thisFile.data.push(thisOldFile.data[rowId]);
let newIndex = newLen-1;
// assign indexIds
thisFile.data.indexIds ||= {};
thisFile.data.indexIds[key] = newIndex;
// assing context
thisFile.context[newIndex] = ["RefreshCarryover/"+contextCounter]
// assign cellInfo
if (thisOldFile.cellInfo?.[rowId]) {
thisFile.cellInfo ||= [];
thisFile.cellInfo[newIndex] = thisOldFile.cellInfo[rowId]
}
if (thisOldFile.comments?.[rowId]) {
thisFile.comments ||= [];
thisFile.comments[newIndex] = thisOldFile.comments[rowId]
}
if (thisOldFile.parameters?.[rowId]) {
thisFile.parameters ||= [];
thisFile.parameters[newIndex] = thisOldFile.parameters[rowId]
}
if (thisOldFile.tags?.[rowId]) {
thisFile.tags ||= [];
thisFile.tags[newIndex] = thisOldFile.tags[rowId]
}
contextCounter++;
continue;
}
// for (let x=1; x<thisOldFile.data[rowId].length; x++) {
// //console.log("%c--Assigning ", "color:aqua", thisOldFile.data[rowId][x], "to col", x);
// thisFile.data[fileRow][x] = thisOldFile.data[rowId][x];
// }
// console.log("%c--Assigning ", "color:aqua", thisOldFile.data[rowId], "to row", rowId);
// assign data. Since the key is identical, we can safely copy the entire row
thisFile.data[fileRow] = thisOldFile.data[rowId]
// assign cellInfo
if (thisOldFile.cellInfo?.[rowId]) {
thisFile.cellInfo ||= [];
thisFile.cellInfo[fileRow] = thisOldFile.cellInfo[rowId]
}
if (thisOldFile.comments?.[rowId]) {
thisFile.comments ||= [];
thisFile.comments[fileRow] = thisOldFile.comments[rowId]
}
if (thisOldFile.parameters?.[rowId]) {
thisFile.parameters ||= [];
thisFile.parameters[fileRow] = thisOldFile.parameters[rowId]
}
if (thisOldFile.tags?.[rowId]) {
thisFile.tags ||= [];
thisFile.tags[fileRow] = thisOldFile.tags[rowId]
}
}
}
}
console.log("updatedProject", projectCopy);
return projectCopy;
}
Trans.prototype.updateProject2 = function(updatedTrans, oldTrans = this.getSaveData()) {
return updatedTrans
}
Trans.prototype.openFromTransObj = function(jsonData, options) {
// open trans object from parsed jsonData or string
if (typeof jsonData == 'string') jsonData = JSON.parse(jsonData);
options = options||{};
options.onAfterLoading = options.onAfterLoading||function(type, responseData, event) {};
options.onSuccess = options.onSuccess||function(responseData, event) {};
options.onFailed = options.onFailed||function(responseData, event) {};
options.filePath = options.filePath||"";
options.isNew = options.isNew || false;
if (options.isNew) {
jsonData = this.initTransData(jsonData);
console.log(jsonData);
}
this.currentFile = options.filePath;
this.applySaveData(jsonData);
this.sanitize();
this.grid.render();
sys.insertOpenedFileHistory();
// apply config to $DV.config;
try {
$DV.config.sl = this.project.options.sl||"ja";
$DV.config.tl = this.project.options.tl||"en";
} catch (e) {
$DV.config.sl = "ja";
$DV.config.tl = "en";
}
if (typeof options.onSuccess == 'function') options.onSuccess.call(this, jsonData);
if (typeof options.onAfterLoading == 'function') options.onAfterLoading.call(this, "success", jsonData);
this.updateStagingInfo();
this.isOpeningFile = false;
ui.hideBusyOverlay();
if (jsonData.project.selectedId) {
this.selectFile(jsonData.project.selectedId);
} else {
this.selectFile($(".fileList .data-selector").eq(0));
}
}
Trans.prototype.detectFormat = async function(path) {
if (!path) return false;
if (nwPath.extname(path).toLowerCase() == ".tpp") return "tpp";
if (nwPath.extname(path).toLowerCase() == ".trans") return "trans";
return false;
}
Trans.prototype.open = async function(filePath, options) {
filePath = filePath||this.currentFile;
if (filePath == "" || filePath == null || typeof filePath == 'undefined') return false;
console.log("opening project : ", filePath);
if (await this.detectFormat(filePath) == "tpp") {
return this.importTpp(filePath);
}
var trans = this;
options = options||{};
options.onAfterLoading = options.onAfterLoading||function(type, responseData, event) {};
options.onSuccess = options.onSuccess||function(responseData, event) {};
options.onFailed = options.onFailed||function(responseData, event) {};
trans.isOpeningFile = true;
ui.showBusyOverlay();
fs.readFile(filePath, function (err, data) {
if (err) {
//throw err;
console.log(err);
alert(t("error opening file (open): ")+filePath+"\r\n"+err);
if (typeof data != 'undefined') {
data = data.toString();
if (typeof options.onFailed =='function') options.onFailed.call(trans, data);
if (typeof options.onAfterLoading =='function') options.onAfterLoading.call(trans, "error", data);
}
ui.hideBusyOverlay();
} else {
data = data.toString();
var jsonData = {};
try {
jsonData = JSON.parse(data);
} catch (e) {
console.warn("Failed to parse JSON data");
alert("Failed to parse JSON data.\nThe .trans file is corrupted.");
if (typeof options.onAfterLoading == 'function') options.onAfterLoading.call(trans, "Failed", jsonData);
trans.isOpeningFile = false;
ui.hideBusyOverlay();
return;
}
console.log(jsonData);
trans.currentFile = filePath;
trans.applySaveData(jsonData);
trans.sanitize();
trans.grid.render();
sys.insertOpenedFileHistory();
// apply config to $DV.config;
try {
$DV.config.sl = trans.project.options.sl||"ja";
$DV.config.tl = trans.project.options.tl||"en";
} catch (e) {
$DV.config.sl = "ja";
$DV.config.tl = "en";
}
if (typeof options.onSuccess == 'function') options.onSuccess.call(trans, jsonData);
if (typeof options.onAfterLoading == 'function') options.onAfterLoading.call(trans, "success", jsonData);
trans.isOpeningFile = false;
ui.hideBusyOverlay();
if (jsonData.project.selectedId) {
trans.selectFile(jsonData.project.selectedId);
} else {
trans.selectFile($(".fileList .data-selector").eq(0));
}
trans.gridIsModified(false);
// open infobox
trans.project.options = trans.project.options || {}
console.log("displaying info ")
if (Boolean(trans.project.options.info) && trans.project.options.displayInfo) {
ui.showPopup("infobox_"+trans.project.projectId, trans.project.options.info, {
title : "Project's Info",
allExternal : true,
HTMLcleanup : true
});
}
// eval whether has error on paths
if (trans.isCacheError()) {
ui.addIconOverlay($(".button-properties"), "attention")
$(".button-properties").attr("title", "Project properties - Staging path error!")
} else {
ui.clearIconOverlay($(".button-properties"))
$(".button-properties").attr("title", "Project properties");
}
}
});
}
Trans.prototype.isCacheError = function() {
trans.project.cache = trans.project?.cache || {}
if (trans.project.cache.cachePath) {
if (common.isDir(trans.project.cache.cachePath) == false) {
return true;
}
}
return false;
}
Trans.prototype.getSl = function() {
// get source language
this.project.options = this.project.options || {}
sys.config.default = sys.config.default || {};
var sl = this.project.options.sl || sys.config.default.sl || $DV.config.sl;
return sl;
}
Trans.prototype.getTl = function() {
// get target language
this.project.options = this.project.options || {}
sys.config.default = sys.config.default || {};
var tl = this.project.options.tl || sys.config.default.tl || $DV.config.tl;
return tl;
}
//================================================================
//
// HANDLING SAVE & LOAD DATA
//
//================================================================
Trans.prototype.applyNewData = function(newData) {
newData = newData||[[]];
for (var row=0; row<newData.length; row++) {
this.data[row] = newData[row];
}
}
Trans.prototype.applyNewHeader = function(newHeader) {
newHeader = newHeader||[];
for (var header=0; header<newHeader.length; header++) {
this.colHeaders[header] = newHeader[header];
}
}
Trans.prototype.generateId = function() {
return common.makeid(10);
}
Trans.prototype.initTransData = function(transData) {
// initialize new trans data created by other application
transData = transData||{};
var template = JSON.parse(fs.readFileSync("data/template.trans"));
// remove identity from template
template.project ||= {}
template.project.projectId = undefined;
template.project.buildOn = undefined;
var result = common.mergeDeep(template, transData);
result.project = result.project || {}
result.project.options = result.project.options || {}
result.project.options.init = result.project.options.init || {}
result.project.projectId = result.project.projectId || this.generateId(10);
result.project.buildOn = result.project.buildOn || common.formatDate(Date.now());
result.project.editorVersion = nw.App.manifest.version;
result.project.editorName = "Translator++";
return result;
}
Trans.prototype.createFileData = function(fullPath, defaultData, options={}) {
defaultData = defaultData || {}
fullPath = fullPath.replace(/\\/g, "/");
if (options?.leadSlash) {
if (fullPath.charAt(0) !== "/") fullPath = "/"+fullPath;
}
defaultData.basename = defaultData.basename||nwPath.basename(fullPath),
defaultData.filename = defaultData.filename||nwPath.basename(fullPath),
defaultData.path = defaultData.path||fullPath,
defaultData.relPath = defaultData.relPath||fullPath,
defaultData.data = defaultData.data||[[null]],
defaultData.originalFormat = defaultData.originalFormat||"Autogenerated TRANS obj",
defaultData.type = defaultData.type||""
defaultData.context = defaultData.context||[]
defaultData.tags = defaultData.tags||[]
if (typeof defaultData.extension == 'undefined') defaultData.extension = nwPath.extname(fullPath);
if (typeof defaultData.dirname == 'undefined') defaultData.dirname = nwPath.dirname(fullPath);
return defaultData;
}
Trans.prototype.validateTransData = function(transData) {
// standarized transData
/*
adapt from two dimensional array
*/
var result = {};
if (Array.isArray(transData)) {
console.log("Case 1 - transData is array");
let templateObj = this.loadJSONSync(this.getTemplatePath());
let objName = "/main";
templateObj.project.files[objName] = this.createFileData(objName, {data:transData});
result = templateObj;
return result;
}
/*
object is in file structure
*/
if (Boolean(transData.project)==false && Boolean(transData.files)==false && Array.isArray(transData.data)) {
console.log("Case 1 - transData is file structured");
let templateObj = this.loadJSONSync(this.getTemplatePath());
let objName = transData.path||"/main";
templateObj.project.files[objName] = this.createFileData(objName, {data:transData.data});
result = templateObj;
return result;
}
if ( Boolean(transData.project)==false && Boolean(transData.files)==true) {
result.project = transData;
} else {
result = transData;
}
result.project.gameTitle = result.project.gameTitle||t("untitled project");
result.project.gameEngine = result.project.gameEngine||"";
result.project.projectId = result.project.projectId||"";
result.project.buildOn = result.project.buildOn || common.formatDate();
result.project.files = result.project.files || {};
for (var id in result.project.files) {
result.project.files[id] = this.createFileData(id, result.project.files[id])
}
return result;
}
/**
* Normalize loaded column header
* Apply default value & unchangeable value into trans.columns
* Should be called each time trans files are loaded
*/
Trans.prototype.normalizeHeader = function() {
for (var i=0; i<this.columns.length; i++) {
if (i == this.keyColumn) {
this.columns[i].validator = this.validateKey;
}
//this.columns[i].trimWhitespace = false;
this.columns[i].wordWrap = true;
}
}
/**
* Load the trans structured data into the current project
* @param {Object} saveData - Trans structured data
* @returns {Object} - Instance of Trans
*/
Trans.prototype.applySaveData = function(saveData) {
console.log("entering trans.applySaveData");
console.log(saveData);
saveData = saveData||{};
saveData = this.validateTransData(saveData);
this.data = saveData.data||[[null]];
//trans.data = [[]];
this.columns = saveData.columns||this.default.columns||[];
this.normalizeHeader();
this.colHeaders = saveData.colHeaders||this.default.colHeaders||[];
this.project = saveData.project||{};
//this.indexIds = saveData.indexIds||{};
// FILLING ROOT VARIABLE based on game project
this.gameTitle = saveData.project.gameTitle||"";
this.gameEngine = saveData.project.gameEngine||"";
this.projectId = saveData.project.projectId||"";
this.gameFolder = saveData.project.loc||"";
// detecting fileList
if (saveData.fileListLoaded == false) {
try {
if (typeof saveData.project.files !== "undefined") saveData.fileListLoaded = true;
}
catch(err) {
saveData.fileListLoaded = false;
}
}
this.resetIndex();
this.fileListLoaded = saveData.fileListLoaded||false;
this.initFileNav();
this.refreshGrid();
ui.setWindowTitle();
ui.setStatusBar(1, this.gameTitle);
//engines.handler('onLoadTrans').apply(this, arguments);
return this;
}
/**
* Create a clone structure of the trans object that ready to be saved
* @param {Object} options
* @param {String} options.type - Type of the returned data (""||"json"||"lz")
* @returns {(Object|String)} - A clone of trans object
*/
Trans.prototype.getSaveData = function(options) {
options = options||{};
options.filter = options.filter || [];
options.type = options.type || ""; // ""||"json"||"lz"
if (empty(this.project)) return;
if (empty(this.project.files)) return;
this.project.projectId = this.project.projectId || common.makeid(10);
//if (!this.project.projectId) return;
var projectClone = JSON.parse(JSON.stringify(this.project))||{};
if (options.filter.length > 0) {
console.log("filtering saved object", options);
if (typeof this.project !== 'undefined') {
if (typeof this.project.files !== 'undefined') {
projectClone.files = {};
for (var i=0; i<options.filter.length; i++) {
var thisId = options.filter[i];
console.log("testing "+thisId, this.project.files[thisId], this.project.files);
if (typeof this.project.files[thisId] == 'undefined') continue;
console.log("exist, assigning "+thisId);
projectClone.files[thisId] = JSON.parse(JSON.stringify(this.project.files[thisId]));
}
}
}
}
var saveData = {};
//saveData.data = this.data||[];
saveData.data = [[null]];
saveData.columns = this.columns||[];
saveData.colHeaders = this.colHeaders||[];
saveData.project = projectClone;
//saveData.indexIds = this.indexIds;
saveData.fileListLoaded = this.fileListLoaded;
// get column width
for (let i=0; i<this.grid.getColHeader().length; i++) {
if (!saveData.columns[i]) continue;
saveData.columns[i].width = this.grid.getColWidth(i);
}
// strip out reference data
for (var fileId in saveData.project.files) {
if (saveData.project.files[fileId].type == 'reference') {
saveData.project.references = saveData.project.references||{};
saveData.project.references[fileId] = JSON.parse(JSON.stringify(saveData.project.files[fileId]));
delete saveData.project.files[fileId];
}
}
if (options.type == "json") return JSON.stringify(saveData);
return saveData;
}
/**
* Compress an object
* @async
* @param {(Buffer|String|Object)} data
* @param {*} options
* @return {Promise<string>} - compressed data
*/
Trans.prototype.compress = async function(data, options) {
options = options || {};
var buff = Buffer.from([]);
if (Buffer.isBuffer(data)) {
buff = data;
} else if (typeof data == "string") {
buff = Buffer.from(data);
} else if (typeof data == "object" && !empty(data)) {
buff = Buffer.from(JSON.stringify(data));
} else {
// generate buffer from trans
buff = Buffer.from(JSON.stringify(this.getSaveData()));
}
return common.gzip(buff, options);
}
/**
* Uncompress a string
* @async
* @param {(Buffer|String)} data - Compressed string
* @param {*} options
* @returns {Promise<string>} - uncompressed string
*/
Trans.prototype.uncompress = async function(data, options) {
options = options || {};
var buff = Buffer.from([]);
if (Buffer.isBuffer(data)) {
buff = data;
} else if (typeof data == "string") {
buff = Buffer.from(data);
} else {
return console.error("Can not uncompress from data:", data, "Only buffer or string is accepted");
}
return common.gunzip(buff, options);
}
/**
* @async
* @param {(Buffer|String)} source - the source object
*/
Trans.prototype.from = async function(source) {
var str = "";
var obj;
if (Buffer.isBuffer(source)) {
if (source.slice(0, 10).join(",") == "31,139,8,0") {
// gziped
str = await common.gunzip(source);
str = str.toString();
} else {
str = source.toString();
}
} else if (typeof source == "string") {
str = source;
}
if (str) {
if (JSON.isJSON(str) == false) return console.error("unknown format :", source);
obj = JSON.parse(str);
}
return obj;
}
/**
* Generate backup from given path of trans file
* @param {String} saveFile - Path to the file
*/
Trans.prototype.createBackup = async function(saveFile) {
var backupLevel = parseInt(sys.getConfig("backupLevel"));
if (!sys.getConfig("autoBackup")) return;
if (!backupLevel) return;
if (!await common.isFileAsync(saveFile)) return console.warn("no such file ", saveFile);
if ([".trans", ".tpp"].includes(nwPath.extname(saveFile).toLowerCase()) == false) return;
// remove last file
await common.unlink(saveFile+`.${backupLevel}.bak`);
for (var i=backupLevel-1; i>=0; i--) {
if (i==0) {
await common.rename(saveFile, saveFile+`.${i+1}.bak`);
} else {
if (await common.isFileAsync(saveFile+`.${i}.bak`) == false) continue;
await common.rename(saveFile+`.${i}.bak`, saveFile+`.${i+1}.bak`)
}
}
}
/**
* Save project into .trans file with the new filename
* This function will open a blocking save dialog.
* @since 4.3.16
* @async
* @param {String} targetFile - Path to the file
* @param {Object} options - Object of the options
* @param {String} options.initiator - Who called the function user||auto
* @param {Function} options.onAfterLoading - Callback after the process is done
* @param {Function} options.onSuccess - Callback after the file is saved successfully
* @param {Function} options.onFailed - Callback when the error is occured
* @returns {Promise<string>} - The path where the file was saved
*/
Trans.prototype.saveAs = async function(targetFile, options) {
var target = await ui.saveAs(targetFile || trans.currentFile || "");
console.log("Saving into : ", target);
if (!target) return "";
return await this.save(target, options);
}
/**
* Save project into .trans file
* @async
* @param {String} targetFile - Path to the file
* @param {Object} options - Object of the options
* @param {String} options.initiator - Who called the function user||auto
* @param {Function} options.onAfterLoading - Callback after the process is done
* @param {Function} options.onSuccess - Callback after the file is saved successfully
* @param {Function} options.onFailed - Callback when the error is occured
* @returns {Promise<string>} - The path where the file was saved
*/
Trans.prototype.save = async function(targetFile, options) {
targetFile = targetFile||this.currentFile;
if (!targetFile) return await this.saveAs(targetFile, options);
var trans = this;
options = options||{};
options.initiator = options.initiator||"user";
options.filter = options.filter || [];
options.onAfterLoading = options.onAfterLoading||function(responseData, event) {};
options.onSuccess = options.onSuccess||function(responseData, event) {};
options.onFailed = options.onFailed||function(responseData, event) {};
if (trans.isSavingFile) return console.warn("Trans.prototype.save() is busy! Please wait until the previous save procedure to be completed before triggering another one!");
// data to save
console.log("Saving data to : "+targetFile);
var saveData = trans.getSaveData(options);
console.log(saveData);
trans.isSavingFile = true;
ui.saveIndicatorStart();
if (options.initiator == "user") await this.createBackup(targetFile);
return new Promise((resolve, reject)=> {
fs.writeFile(targetFile, JSON.stringify(saveData), (err) => {
if (err) {
if (typeof options.onFailed =='function') options.onFailed.call(trans, saveData, targetFile);
ui.saveIndicatorEnd()
console.warn("Failed to save to : ", targetFile, err );
reject();
} else {
if (options.initiator !== "auto") {
console.log(targetFile+' successfully saved!');
sys.insertOpenedFileHistory(targetFile,saveData.project.projectId,saveData.project.gameTitle, options.initiator);
this.currentFile = targetFile;
trans.gridIsModified(false);
ui.setWindowTitle();
}
if (typeof options.onSuccess == 'function') options.onSuccess.call(trans, saveData, targetFile);
resolve(targetFile);
}
options.onAfterLoading.call(trans, saveData, targetFile);
setTimeout(function(){
ui.saveIndicatorEnd()
}, 1000);
trans.isSavingFile = false;
});
})
}
/**
* Generating chache path
*/
Trans.prototype.generateCachePath = function() {
this.project.cache = this.project.cache || {}
this.project.cache.cacheID = this.project.cache.cacheID||this.project.projectId;
if (!this.project.cache.cacheID) this.project.cache.cacheID = this.project.projectId = common.makeid(10)
console.log("Joining", sys.config.stagingPath, this.project.cache.cacheID);
this.project.cache.cachePath = this.project.cache.cachePath || nwPath.join(sys.config.stagingPath, this.project.cache.cacheID);
try {
fs.mkdirSync(this.project.cache.cachePath, {recursive:true})
} catch (e) {
console.warn(e);
}
}
/**
* Trigger auto save procedure
* @async
* @param {Object} options
* @param {String} [options.initiator=auto] - Who call this function
* @param {Function} options.onSuccess - Called when success
* @returns {String} - Path to the saved file if success
*/
Trans.prototype.autoSave = async function(options) {
options = options||{};
if (typeof this.project == 'undefined') {
options.onSuccess = options.onSuccess || function(){};
options.onSuccess.call(this);
return false;
}
try {
if (!this.project.cache.cachePath) {
this.generateCachePath();
} else {
if (!common.isDir(this.project.cache.cachePath)) this.generateCachePath();
}
} catch (e) {
console.warn(e)
}
options.initiator = "auto";
var path = nwPath.join(this.project.cache.cachePath,"autosave.json");
await this.save(path, options);
return path;
}
/**
* Return processed fileData on success. A transmutable function.
* @param {Object} fileData - Object of the file (trans.project.files[filePath])
* @param {Boolean} force - Force rebuilding the index
* @returns {Object} fileData (mutable)
*/
Trans.prototype.buildIndexFromData = function(fileData, force) {
// fileData is file object :
// ex. trans.project.files['main']
// return processed fileData on success
// transmutable function
var key = 0;
if (typeof fileData !== 'object') return console.warn("fileData is not an object");
if (Array.isArray(fileData.data) == false) return console.warn("fileData.data is not a valid array");
if (fileData.indexIsBuilt && !force) return fileData;
fileData.data = fileData.data || [];
fileData.indexIds = fileData.indexIds || {}
for (var row = 0; row<fileData.data.length; row++) {
var thisRow = fileData.data[row];
if (!thisRow[key]) continue;
fileData.indexIds[thisRow[key]] = row;
}
fileData.indexIsBuilt= true
return fileData;
}
/**
* merge externalTrans into targetTrans
* by default targetTrans = current project
* @param {Object} externalTrans - External instance of Trans object to be exported
* @param {Object} [targetTrans=this] - existing instance of Trans object
* @param {Object} options
* @param {Boolean} options.overwrite - Will overwrite if the same file object exist
* @param {Boolean} options.all - Will merge all file object if True
* @param {Object} options.targetPair - Target files
* @param {String[]} options.files - List of source files
* @returns {Object} - merged trans
*/
Trans.prototype.mergeTrans = function(externalTrans, targetTrans, options) {
// merge externalTrans into targetTrans
// bydefault targetTrans = current project;
var key = 0;
targetTrans = targetTrans || this;
targetTrans.project = targetTrans.project || {};
targetTrans.project.files = targetTrans.project.files || {};
externalTrans = externalTrans || {};
externalTrans.project = externalTrans.project || {}
externalTrans.project.files = externalTrans.project.files || {};
options = options || {};
options.overwrite = options.overwrite || false;
options.targetPair = options.targetPair||{};
options.files = options.files || [];
options.all = options.all || false; // fetch all?
if (options.all) options.targetPair = externalTrans.project.files; // if all, doesn't use targetPair
targetTrans = this.sanitize(targetTrans);
var targetIsSelf = false;
if (targetTrans instanceof Trans) targetIsSelf = true;
console.log("running mergeTrans with args : ", arguments);
//return;
for (var id in options.targetPair) {
var sourceFile = externalTrans.project.files[id];
var targetFile = targetTrans.project.files[id];
if (!targetFile) {
// copy entire sourcefile into targetFile
targetTrans.project.files[id] = common.clone(sourceFile);
if (targetIsSelf) this.addFileItem(id, targetTrans.project.files[id]);
continue;
}
console.log("merging data", id);
// the real deal, merge the data
if (Array.isArray(sourceFile.data)==false) continue;
if (sourceFile.data.length == 0) continue;
this.buildIndexFromData(targetFile);
targetFile.context = targetFile.context||[];
sourceFile.context = sourceFile.context||[];
for (var row=0; row<sourceFile.data.length; row++) {
var thisSourceRow = sourceFile.data[row];
if (Boolean(thisSourceRow[key])==false) continue;
var index = targetFile.indexIds[thisSourceRow[key]];
if (typeof index == 'undefined') {
index = targetFile.data.length;
}
targetFile.data[index] = common.clone(thisSourceRow);
targetFile.context[index] = common.clone(sourceFile.context[row]);
}
}
if (targetIsSelf) {
this.generateHeader(targetTrans);
this.evalTranslationProgress();
ui.fileList.reIndex();
ui.initFileSelectorDragSelect();
}
this.refreshGrid();
return targetTrans;
}
/**
* Load JSON file in synchronous fashion
* @param {String} filePath - Path to the JSON file
* @param {Object} options
* @returns {Object} Loaded JSON object
*/
Trans.prototype.loadJSONSync = function(filePath, options) {
if (filePath == "" || filePath == null || typeof filePath == 'undefined') return false;
options = options||{};
options.onAfterLoading = options.onAfterLoading||function(responseData, event) {};
options.onSuccess = options.onSuccess||function(responseData, event) {};
options.onFailed = options.onFailed||function(responseData, event) {};
var fs = require('fs');
var content = fs.readFileSync(filePath);
var resultStr = content.toString();
var result = false;
try {
result = JSON.parse(resultStr);
return result;
} catch(e) {
return result;
}
}
/**
* open JSON & Parse it. General purpose function
* @async
* @param {String} filePath - Path to the JSON file
* @param {Object} options
* @param {Function} options.onSuccess - Called when success
* @param {Function} options.onFailed - Called when failed
*/
Trans.prototype.loadJSON = async function(filePath, options) {
// open JSON & Parse it
// for general purposes
var trans = this;
if (filePath == "" || filePath == null || typeof filePath == 'undefined') return false;
options = options||{};
options.onAfterLoading = options.onAfterLoading||function(responseData, event) {};
options.onSuccess = options.onSuccess||function(responseData, event) {};
options.onFailed = options.onFailed||function(responseData, event) {};
this.isOpeningFile = true;
ui.showBusyOverlay();
var jsonData;
try {
let loadedFile = await common.fileGetContents(filePath);
jsonData = JSON.parse(loadedFile);
} catch (e) {
alert("Error loading file: "+filePath)
}
ui.hideBusyOverlay();
trans.isOpeningFile = false;
return jsonData;
// fs.readFile(filePath, function (err, data) {
// if (err) {
// console.log("error opening file (loadJSON): "+filePath);
// data = data.toString();
// if (typeof options.onFailed =='function') options.onFailed.call(trans, data);
// ui.hideBusyOverlay();
// throw err;
// } else {
// data = data.toString();
// try {
// var jsonData = JSON.parse(data);
// console.log(jsonData);
// } catch (e) {
// alert(t("Can not parse JSON data. Probably the file is corrupted."));
// options.onFailed.call(trans, jsonData);
// trans.isOpeningFile = false;
// ui.hideBusyOverlay();
// return;
// }
// if (typeof options.onSuccess == 'function') options.onSuccess.call(trans, jsonData);
// trans.isOpeningFile = false;
// ui.hideBusyOverlay();
// }
// });
}
/**
* Import from file and replace or create object
* @param {String|Trans} file - Trans file to be imported
* @param {Object} options
* @param {Boolean} options.overwrite - Will overwrite if the same file object exist
* @param {Boolean} options.all - Will merge all file object if True
* @param {Object} options.targetPair - Target files
* @param {String[]} options.files - List of source files
* @param {Boolean} options.mergeData
*/
Trans.prototype.importFromFile = async function(file, options) {
// import from file and replace or create object
// options.targetPair = {
// sourceKey : targetKey
// }
// or
// options.targetPair = {
// sourceKey : true // same with sourceKey
// }
//
// if targetKey is not exist, then create one.
options = options||{};
options.overwrite = options.overwrite || false;
options.targetPair = options.targetPair||{};
options.files = options.files || [];
options.mergeData = options.mergeData || false;
//if (Array.isArray(file) == false) file = [file];
var trans = this;
var data;
if (!this.isTrans(file)) {
data = await trans.loadJSON(file);
data = trans.validateTransData(data);
} else {
data = file;
}
if (options.mergeData) {
trans.mergeTrans(data, trans, options);
return;
}
for (let sourceKey in options.targetPair) {
if (typeof options.targetPair[sourceKey] !== 'string') {
options.targetPair[sourceKey] = sourceKey;
}
try {
//if (typeof trans.project.files[options.targetPair[sourceKey]] == 'undefined') continue;
if (data.project.references[sourceKey] !== 'undefined') {
trans.project.files[options.targetPair[sourceKey]] = data.project.references[sourceKey];
}
if (typeof data.project.files[sourceKey] == 'undefined') continue;
trans.project.files[options.targetPair[sourceKey]] = data.project.files[sourceKey];
console.log(t("create file list"), options.targetPair[sourceKey], trans.project.files[options.targetPair[sourceKey]]);
trans.addFileItem(options.targetPair[sourceKey], trans.project.files[options.targetPair[sourceKey]]);
} catch (e) {
console.log(e);
continue;
}
}
ui.initFileSelectorDragSelect();
trans.evalTranslationProgress();
ui.fileList.reIndex();
trans.refreshGrid();
// trans.loadJSON(file, {
// onSuccess : function(data) {
// data = trans.validateTransData(data)
// if (options.mergeData) {
// trans.mergeTrans(data, trans, options);
// return;
// }
// for (let sourceKey in options.targetPair) {
// if (typeof options.targetPair[sourceKey] !== 'string') {
// options.targetPair[sourceKey] = sourceKey;
// }
// try {
// //if (typeof trans.project.files[options.targetPair[sourceKey]] == 'undefined') continue;
// if (data.project.references[sourceKey] !== 'undefined') {
// trans.project.files[options.targetPair[sourceKey]] = data.project.references[sourceKey];
// }
// if (typeof data.project.files[sourceKey] == 'undefined') continue;
// trans.project.files[options.targetPair[sourceKey]] = data.project.files[sourceKey];
// console.log(t("create file list"), options.targetPair[sourceKey], trans.project.files[options.targetPair[sourceKey]]);
// trans.addFileItem(options.targetPair[sourceKey], trans.project.files[options.targetPair[sourceKey]]);
// } catch (e) {
// console.log(e);
// continue;
// }
// }
// ui.initFileSelectorDragSelect();
// trans.evalTranslationProgress();
// ui.fileList.reIndex();
// trans.refreshGrid();
// }
// });
}
/**
* Select a cell from grid
* @param {Number} row
* @param {Number} column
* @returns {Boolean} True if success
*/
Trans.prototype.selectCell = function(row, column) {
return this.grid.selectCell(row, column);
}
/**
* Generate checksum for the original text
* The checksume is 32bit representation of the original texts
* So the app can determine whether the game is same or not by their respective original texts
* @returns {String} 8 Byte of the project's checksum
*/
Trans.prototype.getProjectChecksum = function() {
if (this.project.checksum) return this.project.checksum;
var sums = [];
var allFiles = this.getAllFiles();
allFiles.sort();
for (var f in allFiles) {
var thisFile = this.project.files[allFiles[f]];
if (empty(thisFile)) continue;
if (empty(thisFile.data)) continue;
// exclude "*" path such as Common Reference
if (thisFile.dirname == "*") continue;
var textPool = [];
for (var r=0; r<thisFile.data.length; r++) {
if (!thisFile.data[r][this.keyColumn]) continue;
textPool.push(thisFile.data[r][this.keyColumn]);
}
if (textPool.length < 1) continue;
textPool.sort();
sums.push(common.crc32String(JSON.stringify(textPool)));
}
this.project.checksum = common.crc32String(JSON.stringify(sums));
return this.project.checksum;
}
/**
* Export project into a TPP file
* @function
* @param {String} file - Path to the tpp file
* @param {*} options
* @param {Function} options.onDone - Triggered when done
*/
Trans.prototype.exportTPP = function(file, options) {
// export translation to TPP
if (typeof file=="undefined") return false;
options = options||{};
options.showDetail = options.showDetail || false;
options.onDone = options.onDone||function() {};
var autofillFiles = [];
var checkbox = $(".fileList .data-selector .fileCheckbox:checked");
for (var i=0; i<checkbox.length; i++) {
autofillFiles.push(checkbox.eq(i).attr("value"));
}
options.files = options.files||autofillFiles||[];
ui.saveIndicatorStart();
trans.autoSave({
onSuccess:function() {
//ui.showLoading();
php.spawn("saveTpp.php", {
args:{
path:file,
password:options.password,
gameFolder:trans.gameFolder,
gameTitle:trans.gameTitle,
projectId:trans.projectId,
gameEngine:trans.gameEngine,
files:options.files,
exportMode:options.mode,
rpgTransFormat:trans.config.rpgTransFormat
},
onData:function(buffer) {
if (options.showDetail) ui.loadingProgress(t("Loading"), buffer, {consoleOnly:true, mode:'consoleOutput'});
},
onError:function(buffer) {
if (options.showDetail) ui.loadingProgress(t("Loading"), buffer, {consoleOnly:true, mode:'consoleOutput', classStr:'stderr'});
},
onDone: function(data) {
//console.log(data);
//ui.hideLoading(true);
if (options.showDetail) {
ui.loadingProgress(t("Finished"), t("All process finished!"), {consoleOnly:false, mode:'consoleOutput'});
ui.showCloseButton();
ui.LoadingAddButton(t("Open Explorer"), function() {
common.openExplorer(file);
});
}
ui.saveIndicatorEnd();
options.onDone.call(trans, data);
}
})
}
});
}
/**
* Load TPP file
* @param {Trans} file - Path to the .tpp file
* @param {Object} options
* @param {Function} options.onDone - Triggered when done
*/
Trans.prototype.importTpp = function(file, options) {
if (typeof file=="undefined") return false;
options = options||{};
options.onDone = options.onDone||function() {};
var doLoadTppToStage = function() {
ui.showLoading();
php.spawn("loadTpp.php", {
args:{
path:file,
password:options.password
},
onData:function(buffer) {
ui.loadingProgress(t("Loading"), buffer, {consoleOnly:true, mode:'consoleOutput'});
},
onError:function(buffer) {
ui.loadingProgress(t("Loading"), buffer, {consoleOnly:true, mode:'consoleOutput', classStr:'stderr'});
},
onDone: function(data) {
//console.log(data);
console.log("done")
//ui.hideLoading(true);
var saveFile = $(".console").find("output.cachepath").text();
saveFile = nwPath.join(saveFile,"autosave.json");
console.log("new cache path : ", saveFile);
ui.loadingProgress(t("Loading"), t("Opening file!"), {consoleOnly:false, mode:'consoleOutput'});
trans.open(saveFile, {
onSuccess: function() {
// writing new cache path
trans.project.cache = trans.project.cache || {}
trans.project.cache.cachePath = nwPath.dirname(saveFile);
ui.loadingProgress(t("Loading"), t("Assigning new staging path : "+trans.project.cache.cachePath), {consoleOnly:false, mode:'consoleOutput'});
ui.loadingProgress(t("Loading"), t("Success!"), {consoleOnly:false, mode:'consoleOutput'});
ui.loadingProgress(t("Finished"), t("All process finished!"), {consoleOnly:false, mode:'consoleOutput'});
ui.showCloseButton();
options.onDone.call(trans, data);
},
onFailed: function() {
ui.loadingProgress(t("Loading"), t("Failed!"), {consoleOnly:false, mode:'consoleOutput'});
ui.loadingProgress(t("Finished"), t("All process finished!"), {consoleOnly:false, mode:'consoleOutput'});
ui.showCloseButton();
options.onDone.call(trans, data);
}
});
}
});
}
trans.closeProject();
trans.autoSave({
onSuccess:function() {
doLoadTppToStage();
}
});
}
/**
* Export current project
* @param {String} file - Path to the file/folder
* @param {Object} options
* @param {Function} options.onDone - Triggered when done
* @param {String} options.mode - Export mode (ex. dir)
* @param {String} options.dataPath - location of data path (data folder). Default is cache path
* @param {String} options.transPath - location of the trans file. Default is using autosave on cache folder
* @param {String[]} options.files - List of the file(s) to be exported
* @param {Object} options.options
* @param {String[]} options.options.filterTag - Filter of the tag
* @param {String} options.options.filterTagMode - Mode of the filter (whitelist||blacklist)
*/
Trans.prototype.export = async function(file, options) {
// export translation
if (typeof file=="undefined") return false;
options = options||{};
options.options = options.options||{};
console.log("Exporting with arguments:", arguments);
//return console.log("Exporting project", arguments);
options.mode = options.mode||"dir";
options.onDone = options.onDone||function() {};
options.dataPath = options.dataPath || ""; // location of data path (data folder). Default is using cache
options.transPath = options.transPath || ""; // location of .trans path to process. Default is using autosave on cache folder
options.options.filterTag = options.options.filterTag|| options.filterTag ||[];
options.options.filterTagMode = options.options.filterTagMode||options.filterTagMode||""; // whitelist or blacklist
options.custom = options.custom || options.options.custom;
delete options.options.custom; // delete options.options.custom to avoid confusion
console.log("exporting project", arguments);
var autofillFiles = [];
var checkbox = $(".fileList .data-selector .fileCheckbox:checked");
for (var i=0; i<checkbox.length; i++) {
autofillFiles.push(checkbox.eq(i).attr("value"));
}
options.files = options.files||autofillFiles||[];
// custom export handler
let thisEngine = trans.project.gameEngine;
if (typeof engines[thisEngine] !== 'undefined') {
if (typeof engines[thisEngine].exportHandler == 'function') {
var halt = await engines[thisEngine].exportHandler.apply(this, arguments);
console.log("Is process halt?", halt);
if (halt) {
await this.projectHook.run("afterExport", options);
return;
}
}
}
var autosavePath = await trans.autoSave();
ui.showLoading();
trans.project.options = trans.project.options || {}
trans.project.options.init = trans.project.options.init || {};
var projectOption = Object.assign(trans.project.options.init, options.options);
php.spawn("export.php", {
args:{
path :file,
gameFolder :trans.gameFolder,
gameTitle :trans.gameTitle,
projectId :trans.projectId,
gameEngine :trans.gameEngine,
files :options.files,
exportMode :options.mode,
options :projectOption,
rpgTransFormat:trans.config.rpgTransFormat,
dataPath :options.dataPath,
transPath :nwPath.resolve(options.transPath || autosavePath)
},
onData:function(buffer) {
ui.loadingProgress(t("Loading"), buffer, {consoleOnly:true, mode:'consoleOutput'});
},
onError:function(buffer) {
ui.loadingProgress(t("Loading"), buffer, {consoleOnly:true, mode:'consoleOutput', classStr:'stderr'});
},
onDone: async (data) => {
//console.log(data);
console.log("done")
//ui.hideLoading(true);
ui.loadingProgress(t("Finished"), t("All process finished!"), {consoleOnly:false, mode:'consoleOutput'});
//ui.showCloseButton();
await this.projectHook.run("afterExport", options);
ui.loadingEnd();
options.onDone.call(trans, data);
}
})
}
/**
* Revert the original data into a folder
* Will replace the existing file on the destination directory
* @param {String} destinationPath
* @param {Object} options
*/
Trans.prototype.revertToOriginal = async function(destinationPath, options={}) {
var bCopy = require('better-copy')
if (!destinationPath) {
destinationPath = await ui.openRevertToOriginalDialog();
}
options.dataPath = options.dataPath || "data"
if (!destinationPath) return; // canceled
ui.showLoading();
ui.loadingProgress(0, "Reverting original data");
var objects = this.project.files;
if (this.getCheckedFiles().length > 0) objects = this.getCheckedObjects();
var copied = 0;
var processed = 0;
var totalLength = Object.keys(objects).length || 1;
ui.log(`Processing ${totalLength} file(s)`);
for (var i in objects) {
processed++;
var stagingBaseFolder = this.getStagingDataPath();
var stagingFile = this.getStagingFile(objects[i]);
var relativePath = common.getRelativePath(stagingFile, stagingBaseFolder)
await ui.loadingProgress(Math.round((processed/totalLength)*100), `Reverting : ${relativePath}`);
if (! await common.isFileAsync(stagingFile)) {
await ui.log(`File not found: ${stagingFile}`);
continue;
}
await ui.log(`${processed}/${totalLength} Copying : ${stagingFile}`);
await bCopy(stagingFile, nwPath.join(destinationPath, relativePath), {
overwrite:true
});
copied++;
}
await ui.log(`Completed! ${copied} file(s) reverted to original!`);
ui.LoadingAddButton("Open folder", function() {
nw.Shell.showItemInFolder(nwPath.join(destinationPath, relativePath));
},
{
class: "icon-folder-open"
});
ui.loadingEnd();
}
/**
* Import sheet into the current project
* @param {String} paths - Path to the sheet file / folder
* @param {Number} [columns=1] - Index of the destination column to put the translation into
* @param {Object} options
* @param {String} [options.sourceColumn=auto] - The source column
* @param {Boolean} [options.overwrite=false] - When True will overwrite the existing value
* @param {String[]} options.files - List of targeted files
* @param {Number} [options.sourceKeyColumn=0] - Column index of the translation's key
* @param {Number} [options.keyColumn=0] - Key column of the current project
* @param {Boolean} options.stripCarriageReturn - Whether to strip the carriage retrun character or not
*/
Trans.prototype.importSheet = async function(paths, columns, options) {
// import sheets from a folder or file
columns = columns||1; // target Column
options = options||{};
options.sourceColumn = options.sourceColumn||"auto";
options.overwrite = options.overwrite||false;
options.files = options.files||trans.getCheckedFiles()||[];
options.sourceKeyColumn = options.sourceKeyColumn||0;
options.keyColumn = options.keyColumn||0;
options.newLine = options.newLine||undefined;
options.stripCarriageReturn = options.stripCarriageReturn||false;
options.ignoreNewLine = true; // let's set to true;
console.log("trans.importSheet");
console.log(arguments);
//return console.log("halted");
if (Array.isArray(paths) == false) paths=[paths];
ui.showLoading();
ui.loadingProgress(0, t("Collecting paths"), {consoleOnly:true, mode:'consoleOutput'});
ui.log("Building indexes")
if (options.files?.length == 0) {
options.files = this.getAllFiles();
}
options.indexes = this.buildIndexes(options.files, false);
var allPaths = [];
for (let i=0; i<paths.length; i++) {
var path = paths[i];
if (common.isExist(path) == false) {
ui.loadingProgress(0, t("Path : '")+path+t("' doesn't exist"), {consoleOnly:true, mode:'consoleOutput'});
console.log("Error, path not exist");
continue;
}
if (common.isDir(path)) {
var dirTree = common.dirContentSync(path);
allPaths = allPaths.concat(dirTree);
} else {
allPaths.push(path);
}
}
ui.loadingProgress(0, allPaths.length+t(" file(s) collected!"), {consoleOnly:true, mode:'consoleOutput'});
ui.loadingProgress(0, t("Start importing data!"), {consoleOnly:true, mode:'consoleOutput'});
var processFile;
if (common.parseSheet) {
ui.log("Import using sheet parser add-on")
processFile = async function(filePath) {
var data = await common.parseSheet(filePath);
var merged = [];
for (var i in data) {
console.log("Merging data", data[i]);
if (!data[i]) continue;
if (!data[i].length) continue;
merged = merged.concat(data[i]);
}
console.log("output data :");
console.log(merged);
trans.translateFromArray(merged, columns, options);
}
} else {
ui.log("Import using legacy sheet importer")
processFile = function(filePath) {
filePath = filePath||path;
php.spawnSync("import.php", {
args:{
//'path':'F:\\test\\export',
'path':filePath,
output:null,
mergeSheet:true,
prettyPrint:true
},
onDone : function(data) {
console.log("output data :");
console.log(data);
trans.translateFromArray(data, columns, options);
}
});
}
}
for (let i=0; i<allPaths.length; i++) {
var thisFile = allPaths[i];
ui.loadingProgress(Math.round(i/allPaths.length*100), t("Importing : ")+thisFile, {consoleOnly:true, mode:'consoleOutput'});
await processFile(thisFile);
ui.loadingProgress(Math.round((i+1)/allPaths.length*100), t("Done!"), {consoleOnly:true, mode:'consoleOutput'});
}
trans.refreshGrid();
trans.evalTranslationProgress();
ui.loadingProgress(t("Done!"), t("All Done!"), {consoleOnly:true, mode:'consoleOutput'});
ui.loadingEnd();
}
/**
* Import translation from RPGMTransPatch
* @param {String} paths - Path to the sheet file / folder
* @param {Number} [columns=1] - Index of the destination column to put the translation into
* @param {Object} options
* @param {String} [options.sourceColumn=auto] - The source column
* @param {Boolean} [options.overwrite=false] - When True will overwrite the existing value
* @param {String[]} options.files - List of targeted files
* @param {Number} [options.keyColumn=0] - Key column of the current project
* @param {Boolean} options.stripCarriageReturn - Whether to strip the carriage retrun character or not
*/
Trans.prototype.importRPGMTrans = function(paths, columns, options) {
// Import translation from RPGMTransPatch
columns = columns||1; // target Column
options = options||{};
options.sourceColumn = options.sourceColumn||"auto";
options.overwrite = options.overwrite||false;
options.files = options.files||trans.getCheckedFiles()||[];
options.sourceKeyColumn = options.sourceKeyColumn||0;
options.keyColumn = options.keyColumn||0;
options.newLine = options.newLine||undefined;
options.stripCarriageReturn = options.stripCarriageReturn||false;
options.ignoreNewLine = true; // let's set to true;
console.log("trans.importRPGMTrans");
console.log(arguments);
//return console.log("halted");
if (Array.isArray(paths) == false) paths=[paths];
ui.showLoading();
ui.loadingProgress(0, t("Collecting paths"), {consoleOnly:true, mode:'consoleOutput'});
var allPaths = [];
for (var i=0; i<paths.length; i++) {
var path = paths[i];
if (common.isExist(path) == false) {
ui.loadingProgress(0, t("Path : '")+path+t("' doesn't exist"), {consoleOnly:true, mode:'consoleOutput'});
console.log("Error, path not exist");
continue;
}
if (common.isDir(path)) {
var dirTree = common.dirContentSync(path);
allPaths = allPaths.concat(dirTree);
} else {
allPaths.push(path);
}
}
ui.loadingProgress(0, allPaths.length+t(" file(s) collected!"), {consoleOnly:true, mode:'consoleOutput'});
ui.loadingProgress(0, t("Start importing data!"), {consoleOnly:true, mode:'consoleOutput'});
var processFile = function(filePath) {
filePath = filePath||path;
php.spawnSync("parseTrans.php", {
args:{
'path':filePath,
prettyPrint:true
},
onDone : function(data) {
console.log("output data :");
console.log(data);
if (Array.isArray(data.data)) {
trans.translateFromArray(data.data, columns, options);
}
}
});
}
for (let i=0; i<allPaths.length; i++) {
var thisFile = allPaths[i];
ui.loadingProgress(Math.round(i/allPaths.length*100), t("Importing : ")+thisFile, {consoleOnly:true, mode:'consoleOutput'});
processFile(thisFile);
ui.loadingProgress(Math.round((i+1)/allPaths.length*100), t("Done!"), {consoleOnly:true, mode:'consoleOutput'});
}
trans.refreshGrid();
trans.evalTranslationProgress();
ui.loadingProgress(t("Done!"), t("All Done!"), {consoleOnly:true, mode:'consoleOutput'});
ui.loadingEnd();
}
// ===============================================================
// STATUS BAR
//================================================================
/**
* Clear the status bar
*/
Trans.prototype.clearFooter = function() {
$(".footer .footer1 span").html("")
$(".footer .footer2 span").html("")
$(".footer .footer3 span").html("")
$(".footer .footer4 span").html("")
$(".footer .footer5 span").html("")
}
/**
* Set the value of the current context into the status menu
* @param {Number} row - Selected row
*/
Trans.prototype.setStatusBarContext = function(row) {
if (typeof row== 'undefined') {
if (Array.isArray(trans.grid.getSelected())) row = trans.grid.getSelected()[0][0];
}
//if (typeof row == 'undefined') return false;
//if (typeof trans.project == 'undefined') return false;
//console.log(trans.project.files[trans.getSelectedId()].context[row]);
var currentId = trans.getSelectedId();
try {
if (trans.project.files[currentId].originalFormat == '> ANTI TES PATCH FILE VERSION 0.2' && this.project.parser !== "rmrgss") {
//$(".footer .footer1>span").html(currentId+"/"+trans.buildContextFromParameter(trans.project.files[currentId].parameters[row]));
$(".footer .footer1>span").html(trans.buildContextFromParameter(trans.project.files[currentId].parameters[row]));
} else {
//$(".footer .footer1>span").html(currentId+"/"+trans.project.files[currentId].context[row]);
$(".footer .footer1>span").html(trans.project.files[currentId].context[row].join("; "));
}
$(".footer .footer1>span").addClass("icon-th-2")
}
catch(err) {
$(".footer .footer1>span").html("");
$(".footer .footer1>span").removeClass("icon-th-2")
}
}
/**
* Set the Number of row section of the status bar
* @returns {Boolean} False on fail
*/
Trans.prototype.setStatusBarNumData = function() {
if (typeof trans.project == 'undefined') return false;
try {
$(".footer .footer3 span").html("rows : "+trans.project.files[trans.getSelectedId()].data.length);
}
catch(err) {
$(".footer .footer3 span").html("");
}
}
/**
* Set the engine information section of the status bar
*/
Trans.prototype.setStatusBarEngine= function() {
if (typeof trans.project == 'undefined') return false;
try {
var parser = "";
if (trans.project.parser) parser = `/<span title='parser'>${trans.project.parser}</span>`
$(".footer .footer4 span").html(trans.project.gameEngine+parser);
}
catch(err) {
$(".footer .footer4 span").html("");
}
}
/**
*
* @param {String} type - Type of the icon (notice, warning, notice, translatorPlusPlus)
*/
Trans.prototype.setTrayIcon = function(type) {
// type : notice, warning, notice, translatorPlusPlus
type = type || "translatorPlusPlus";
var $icon = $('<i class="icon trayIcon"></i>');
$icon.addClass(type);
$(".footer .footer5 span").html($icon);
}
//================================================================
//
// EDITOR SECTION PART
//
//================================================================
/**
* Go to the botom most part of the grid to the new key section
*/
Trans.prototype.goToNewKey = function() {
if (Array.isArray(this.data) == false) return false;
this.grid.scrollViewportTo(this.data.length-1);
this.grid.selectCell(this.data.length-1,0);
//if ($(document.activeElement).is("#currentCellText"));
}
/**
* Clear text editor. Bottom right editor.
* @returns {Boolean} True if success
*/
Trans.prototype.clearEditor = function() {
const $cellText = $("#currentCellText");
$cellText.val("");
$cellText.prop("readonly", true);
$cellText.data("column", null);
$cellText.data("row", null);
return true;
}
/**
* Clear the Current Cell info section
* @returns {Boolean} True if success
*/
Trans.prototype.clearCellInfo = function() {
const $cellInfo = $("#currentCoordinate");
$cellInfo.val("");
return true;
}
/**
* Set value of the Current Cell info section
* @param {Number} row
* @param {Number} column
*/
Trans.prototype.setCellInfo = function(row, column) {
const $cellInfo = $("#currentCoordinate");
var drawedRow = row+1;
var drawedCol = column+1;
$cellInfo.val(drawedRow+","+drawedCol);
trans.lastSelectedCell = [row, column];
}
Trans.prototype.drawCellEmblem = function() {
const row = this.lastSelectedCell[0]
const col = this.lastSelectedCell[1]
$(".cellEmblems > i").addClass("hidden");
if (col == this.keyColumn) return;
$(".cellEmblems > .emblemComment").attr("title", "");
if (this.isOrganicCell(row, col)) $(".cellEmblems > .emblemOrganic").removeClass("hidden")
if (this.isVisitedCell(row, col)) {
$(".cellEmblems > .emblemFootprint").removeClass("hidden")
} else {
$(".cellEmblems > .emblemFirstVisit").removeClass("hidden")
}
let comment = this.getCellComment(row, col);
if (comment) {
$(".cellEmblems > .emblemComment").removeClass("hidden")
$(".cellEmblems > .emblemComment").attr("title", "<b>Cell comment:</b><br />"+comment);
}
}
/**
* connect this.data to trans.project.files[trans.getSelectedId()].data
*/
Trans.prototype.connectData = function() {
// connect this.data to trans.project.files[trans.getSelectedId()].data
if (!this.getSelectedId()) return console.warn("unable to connect data with selected id");
if (!this.project.files[this.getSelectedId()].data) return console.warn("unable to connect data with selected id");
this.project.files[this.getSelectedId()].data = trans.data;
}
/**
* Check whether row is the last row
* @param {Number} row - Row index to be checked
* @returns {Boolean} True if row is the last row
*/
Trans.prototype.isLastRow = function(row) {
//trans.data = trans.data || [];
if (Boolean(trans.data) == false) {
trans.data = [];
trans.connectData();
}
row = row || 0;
if (row == trans.data.length-1) return true;
return false;
}
/**
* Get the currently active Table
* @returns {String[][]} Two dimensional array of the data
*/
Trans.prototype.getCurrentData = function() {
return this.data;
}
/**
* Get text from the last selected cell
* @returns {String} Text of the last selected cell
*/
Trans.prototype.getTextFromLastSelected = function() {
var data = this.getCurrentData();
if (empty(this.lastSelectedCell)) return "";
return data[this.lastSelectedCell[0]][this.lastSelectedCell[1]];
}
/**
* Get the value of the Text Editor field
* @param {String} text Value of the Text Editor field
* @param {Boolean} [triggerEvent=false] - Trigger change event
*/
Trans.prototype.textEditorSetValue = function(text, triggerEvent=false) {
$("#currentCellText").val(text);
ui.generateBackgroundNumber();
if (triggerEvent) $("#currentCellText").trigger("change")
}
/**
* Standard procedure executed after selecting cells
* @param {Number} row - Row from
* @param {Number} column - Column from
* @param {Number} row2 - Row to
* @param {Number} column2 - Column to
*/
Trans.prototype.doAfterSelection = function(row, column, row2, column2) {
const $editor = $("#currentCellText");
//$editor.val(this.getValue());
//$editor.val(trans.grid.getCellMeta(row,column).instance.getValue());
$editor.val(trans.data[row][column]);
$editor.prop("readonly", false);
var isLastRow = trans.isLastRow(row)
if (column == 0) {
if ( isLastRow == false) {
$editor.prop("readonly", true);
}
}
$editor.data("column", column);
$editor.data("row", row);
trans.setCellInfo(row, column);
trans.setStatusBarContext(row);
trans.translateSelectedRow(row);
ui.generateBackgroundNumber($editor);
if (typeof romaji !== 'undefined') {
if (trans.config.loadRomaji == false ) return true;
romaji.resolve(trans.data[row][0], $("#currentRomaji .text"));
}
// romaji header
$("#currentRomaji .header").text(this.getRowInfoText(row, true) || "");
this.drawCellEmblem();
// leave trail with debounce
if (this.getOption("gridInfo")?.isRuleActive && this.getOption("gridInfo")?.enableTrail) {
if (this._cellInfoTrack) clearTimeout(this._cellInfoTrack);
if (column != this.keyColumn && this.getText(row, column)) {
this._cellInfoTrack = setTimeout(()=> {
clearTimeout(this._cellInfoTrack);
this._cellInfoTrack = undefined;
console.log("Setting cellInfo", "v", 1, this.getSelectedId(), row, column);
this.cellInfo.set("v", 1, this.getSelectedId(), row, column);
$(`table tbody td[data-coord="${row}-${column}"]`).addClass("viewed")
}, 1000)
}
}
const thisObj = this.getSelectedObject();
thisObj.lastSelectedCell = [row, column];
/**
* Trigger event right after a cell(s) is selected
* @event Trans#onAfterSelectCell
* @param {Object} Options
* @param {Number} Options.fromRow
* @param {Number} Options.fromCol
* @param {Number} Options.toRow
* @param {Number} Options.toCol
* @param {Boolean} Options.isLastRow
*/
this.trigger("onAfterSelectCell",
{
fromRow:row, fromCol:column, toRow:row2, toCol:column2, isLastRow:isLastRow
}
);
}
//================================================================
//
// HANDLING FILE NAVIGATION
//
//================================================================
/**
* Resets current cell editor
*/
Trans.prototype.resetCurentCellEditor = function() {
var $currentCellText = $("#currentCellText");
$currentCellText.val("");
$currentCellText.data("row", 0)
$currentCellText.data("column", 0)
trans.setCellInfo(0, 0);
trans.setStatusBarContext(0);
$("#currentRomaji .text").text("");
$("#currentRomaji .header").text("");
}
/**
* Creates a new file(new object), register it into the left panel
* @param {String} filename - Name of the file
* @param {String} dirname - Directory location
* @param {Object} options
* @param {String} options.originalFormat - Original format of the file
* @param {String} options.type - File type
* @returns {Object}
*/
Trans.prototype.createFile = function(filename, dirname, options) {
// Create a new file
// register it into the left panel
options = options || {};
options.originalFormat = options.originalFormat || ""
options.type = options.type || null
var isValid = require('is-valid-path');
dirname = dirname || "/"
if (!isValid(filename)) {
return {
error:true,
msg : filename+ t(" is not a valid object name")
}
}
if (!isValid(dirname) && dirname !== "*") {
return {
error:true,
msg : dirname+ t(" is not a valid directory name")
}
}
var fullPath = nwPath.join("/", dirname, filename);
var fileObj;
if (this.getObjectById(fullPath)) {
return {
error:true,
msg : fullPath+ t(" is already exist")
}
}
if (dirname == "*") {
// create a reference
fullPath = filename;
options.type = options.type || "reference"
fileObj = this.createFileData(fullPath, {
originalFormat : options.originalFormat || "TRANSLATOR++ GENERATED TABLE",
type : options.type,
dirname : "*"
});
} else {
fullPath = fullPath.replace(/\\/g, "/");
fileObj = this.createFileData(fullPath, {
originalFormat : options.originalFormat,
type : options.type
});
}
trans.project.files[fullPath] = fileObj;
trans.addFileItem(fullPath, fileObj);
this.evalTranslationProgress();
ui.fileList.reIndex();
ui.initFileSelectorDragSelect();
return {}
}
/**
* Select of
* @param {JQuery} $element - Selected element
* @param {Object} options
* @param {Function} options.onDone - When selected done
* @returns {JQuery} - Instance of jquery of the selected element
*/
Trans.prototype.selectFile = function($element, options) {
options = options||{};
options.onDone = options.onDone||undefined;
this.grid.deselectCell()
if (typeof $element == "string") $element = $(".fileList [data-id="+common.escapeSelector($element)+"]");
const thisID = $element.closest("li").data("id");
this.trigger("beforeSelectFile", [trans?.project?.selectedId, thisID])
//console.log("switching to other file");
$element.closest(".tree").find("li").removeClass("selected");
$element.closest("li").addClass("selected");
trans.project.selectedId = thisID;
trans.data = trans.project.files[thisID].data;
//trans.indexIds = trans.project.files[thisID].indexIds;
trans.selectedData = trans.project.files[thisID];
// force reindexig each build
trans.buildIndex();
trans.clearCellInfo();
trans.clearEditor();
trans.setStatusBarNumData();
trans.resetCurentCellEditor();
if ($(".menu-button.addNote").hasClass("checked")) {
ui.openFileNote();
}
$(".fileId").val(thisID);
ui.disableGrid(false);
ui.evalFileNoteIcon();
this.trigger("objectSelected", thisID);
this.grid.loadData(trans.data);
//trans.loadComments();
//trans.refreshGrid({onDone:options.onDone});
//trans.grid.render();
trans.loadComments();
trans.renderGridInfo();
trans.grid.setFixedTableHeightByData(trans.data)
trans.grid.scrollViewportTo(0, 0);
// const thisObjLastSelectedCell = trans.project?.files?.[thisID]?.lastSelectedCell;
// if (Array.isArray(thisObjLastSelectedCell)) {
// trans.grid.scrollViewportTo(thisObjLastSelectedCell[0], thisObjLastSelectedCell[1]);
// trans.grid.selectCell(thisObjLastSelectedCell[0], thisObjLastSelectedCell[1]);
// }
return $element;
}
Trans.prototype.renderGridInfo = function() {
// render options in trans.project.options.gridInfo
let gridInfo = this.getOption("gridInfo") || {};
if (gridInfo?.isRuleActive && gridInfo?.rowHeaderInfo) {
let rowHeaderWidth = gridInfo.rowHeaderWidth||130
trans.grid.updateSettings({
rowHeaderWidth: rowHeaderWidth
});
$("#table").css("--row-header-width", rowHeaderWidth+"px")
} else {
trans.grid.updateSettings({
rowHeaderWidth: null
})
const getWidth = $(`#table [data-role="tablecorner"]`).outerWidth();
$("#table").css("--row-header-width", getWidth+"px")
}
}
/**
* Add a new filegroup
* @param {String} dirname - name of the group
* @returns {Boolean} True if success
*/
Trans.prototype.addFileGroup = function(dirname, fileObj) {
var $group = $("#fileList [data-group='"+CSS.escape(dirname)+"']");
//console.log("Group : ", dirname , $group.length);
if ($group.length < 1) {
//console.log("creating new header");
var hTemplate = $("<li class='group-header' data-group='"+dirname+"'>"+dirname+"</li>");
if ($("#fileList .fileListUl .group-header[data-group='*']").length > 0) {
$("#fileList .fileListUl .group-header[data-group='*']").before(hTemplate);
} else {
$("#fileList .fileListUl").append(hTemplate);
}
return true;
}
return false;
}
/**
* Check whether the ui element of the file is exist or not
* @param {String} file - File id
* @param {Object} fileObj - File object
* @returns {Boolean} True if exist
*/
Trans.prototype.fileItemExist = function(file, fileObj) {
if ($("#fileList [data-group='"+CSS.escape(fileObj.dirname)+"'][data-id='"+CSS.escape(file)+"']").length>0) {
return true;
}
return false;
}
/**
* Draw file status
* @param {String[]} files - list of file ID
*/
Trans.prototype.drawFileStatus = function(files) {
if (typeof files == "string") files = [files];
var $container = $("#fileList");
for (var i=0; i<files.length; i++) {
(()=> {
var thisFile = files[i];
var thisObj = this.getObjectById(thisFile);
var $li = $container.find(`[data-id="${CSS.escape(files[i])}"]`);
if ($li.length == 0) return;
$li.removeClass("isCompleted");
$li.removeClass("isRequireAttention");
if (thisObj.isCompleted) $li.addClass("isCompleted");
if (thisObj.isRequireAttention) $li.addClass("isRequireAttention");
// handling note
//$markersWrapper.empty();
if ($li.data("noteTooltipIsActive")) {
$li.tooltip("destroy");
$li.data("noteTooltipIsActive", false);
}
var $markersWrapper = $li.find(".markers");
$markersWrapper.empty();
if (thisObj.note) {
// add keyword
$markersWrapper.append($(`<span class="hidden"></span>`).text(thisObj.note))
var $noteIcon = $(`<i class="icon-commenting"></i>`);
$markersWrapper.append($noteIcon);
if (thisObj.noteColor) $noteIcon.css("color", thisObj.noteColor);
// for some reason I can not hook the mouse events on $noteIcon
// so I hook it into $li instead. This behavior is acceptable for now.
$li.tooltip({
content: function() {
var $tooltip = $("<div class='fileObjTooltipWindow'></div>");
if (thisObj.noteColor) {
$tooltip.css("border-left-color", thisObj.noteColor);
$tooltip.addClass("hasColor")
}
$tooltip.text(thisObj.note);
$tooltip.on("mouseenter", function() {
ui.fileObjectTooltip.open($tooltip.clone(), $li)
})
return $tooltip;
},
tooltipClass: "fileObjTooltipWrapper",
show: {
effect: "fade",
duration: 100
},
hide: {
effect: "none",
delay: 100
},
position: {
my: "left top",
at: "right+6 top-16",
of: $li
},
open: function( event, ui ) {
}
});
$li.data("noteTooltipIsActive", true);
}
})()
}
}
/**
* Add file item into the left panel
* @param {String} file - File ID
* @param {fileObj} fileObj - File Object
* @returns {Boolean}
*/
Trans.prototype.addFileItem = function(file, fileObj) {
// skip if exist
if (this.fileItemExist(file, fileObj)) return false;
// draw header if exist
this.addFileGroup(fileObj.dirname);
var $li = $("<li></li>");
$li.append("<input type='checkbox' class='fileCheckbox' title='hold shift for bulk selection' />"+
"<a href='#' class='filterable'><span class='filename'></span>"+
"<span class='markers'></span>"+
"<span class='percent' title='progress'></span>"+
"<div class='progress' title='progress'></div>"+
"</a>");
$li.attr("title", file);
$li.attr("data-group", fileObj.dirname);
$li.find(".fileCheckbox").attr("value", file);
$li.find(".filename").text(fileObj.filename);
$li.addClass("data-selector");
$li.data("id", file);
$li.attr("data-id", file);
//$li.data("data", fileObj);
$li.find("a").on("mousedown", function(e) {
//console.log("middle click clicked");
//console.log(e);
if( e.which == 2 ) {
e.preventDefault();
trans.clearSelection();
return false;
}
});
$li.find("a").on("dblclick", function(e) {
// select that item
var $thisCheckbox = $(this).closest("li").find(".fileCheckbox");
$thisCheckbox.prop("checked", !$thisCheckbox.prop("checked")).trigger("change")
});
$li.find("a").on("click", function(e) {
//console.log("clicked");
e.preventDefault();
trans.selectFile($(this).closest("li"));
});
$li.find(".fileCheckbox").on("change", function() {
if ($(this).prop("checked") == true) {
trans.$lastCheckedFile = $(this);
$(this).closest(".data-selector").addClass("hasCheck");
} else {
trans.$lastCheckedFile = undefined;
$(this).closest(".data-selector").removeClass("hasCheck");
}
});
$li.find(".fileCheckbox").on("mousedown", function(e) {
if (!trans.$lastCheckedFile) return false;
if (e.shiftKey) {
console.log("The SHIFT key was pressed!");
var $checkBoxes = $(".fileList .fileCheckbox");
var lastIndex = $checkBoxes.index(trans.$lastCheckedFile);
var thisIndex = $checkBoxes.index(this);
var chckFrom;
var chckTo;
if (lastIndex < thisIndex) {
chckFrom = lastIndex;
chckTo = thisIndex;
} else {
chckFrom = thisIndex;
chckTo = lastIndex;
}
console.log("check from index "+chckFrom+" to "+chckTo);
for (var i=chckFrom; i<chckTo; i++) {
$checkBoxes.eq(i).prop("checked", true).trigger("change");
}
}
});
$("#fileList [data-group='"+CSS.escape(fileObj.dirname)+"']").last().after($li);
}
/**
* Draw the left panel
* @fires Trans#onLoadTrans
*/
Trans.prototype.drawFileSelector = function() {
if (typeof this.project.files == 'undefined') return false;
$("#fileList .fileListUl").empty();
this.generateNewDictionaryTable();
for (var file in this.project.files) {
this.addFileItem(file, this.project.files[file]);
}
this.drawFileStatus(this.getAllFiles());
this.setStatusBarEngine();
this.evalTranslationProgress();
ui.fileList.reIndex();
ui.initFileSelectorDragSelect();
ui.enableButtons();
this.renderGridInfo();
var TranslationByContext = require("www/js/TranslationByContext.js");
ui.translationByContext = new TranslationByContext();
//engines.handler('onLoadTrans').apply(this, arguments);
this.inProject = true;
engines.current().triggerHandler('onLoadTrans', this, arguments);
this.initLocalStorage();
/**
* Trigger event after trans file is loaded
* @event Trans#onLoadTrans
*/
this.trigger('onLoadTrans');
}
Trans.prototype.initLocalStorage = async function() {
let thisProjectDB = trans?.project?.projectId || "global"
this.localStorage = new (require("better-localstorage"))("tp"+thisProjectDB);
}
Trans.prototype.unInitLocalStorage = async function() {
if (typeof this.localStorage?.db?.close == "function") await this.localStorage.db.close();
this.localStorage = undefined;
}
/**
* Mark a file as complete
* @param {Boolean} mark - True if complete
* @param {String[]} [files=this.getAllFiles()] - List of the file IDs
* @returns {Boolean}
*/
Trans.prototype.setMarkAsComplete = function(mark, files) {
files = files || trans.getCheckedFiles();
if (files.length < 1) files = trans.getAllFiles();
for (var i=0; i<files.length; i++) {
var thisFile = files[i];
this.getObjectById(thisFile).isCompleted = mark;
}
this.drawFileStatus(files);
return true;
}
/**
* select all with matching filter
* @param {(String|String[])} [filter=All] - List of the selected file ID. When empty then all will be selected
* @param {Boolean} append - If true, then add into the previous selection
*/
Trans.prototype.selectAll = function(filter, append) {
filter = filter||[];
append = append||false;
if (typeof filter == 'string') filter = [filter];
var $checkBoxes = $("#fileList .fileCheckbox");
if (filter.length == 0) {
$checkBoxes.each(function() {
$(this).prop("checked", true).trigger("change");
});
} else {
if (!append) $checkBoxes.prop("checked", false).trigger("change");
$checkBoxes.each(function() {
var $this = $(this);
if (filter.includes($this.closest("li").data("id"))) $this.prop("checked", true).trigger("change");
});
}
}
/**
* Inverts current selection
*/
Trans.prototype.invertSelection = function() {
var $checkBoxes = $("#fileList .fileCheckbox");
$checkBoxes.each(function() {
$(this).prop("checked", !$(this).prop("checked")).trigger("change");
});
}
/**
* Clear current selection
*/
Trans.prototype.clearSelection = function() {
var $checkBoxes = $("#fileList .fileCheckbox");
$checkBoxes.each(function() {
$(this).prop("checked", false).trigger("change");
});
}
/**
* Initialize file navigator
*/
Trans.prototype.initFileNav = function() {
//console.log(trans.fileListLoaded);
//this function will be executed whenever initializing a new trans file
//this function is suitable to hook all initialization event of trans
console.log("running trans.initFileNav");
//console.log("current trans : ", trans);
// reevaluating trans.fileListLoaded based on existance of trans.project.files
try {
if (typeof trans.project.files !=='undefined') {
trans.fileListLoaded = true;
} else {
trans.fileListLoaded = false;
}
} catch (e) {
trans.fileListLoaded = false;
}
if (trans.fileListLoaded == false) {
trans.createProject({
onAfterLoading:function() {
trans.drawFileSelector();
}
});
return false;
} else {
this.unInitFileNav();
trans.drawFileSelector();
this.onFileNavLoaded.call(this);
//engines.handler('onLoadTrans').apply(this, arguments);
/**
* Triggers after trans loaded
* @event Trans#transLoaded
* @param {Trans} this - Instance of trans
*/
this.trigger("transLoaded", this);
}
}
/**
* Un-initialize file navigator
* @fires Trans#onUnloadTrans
* @fires Engines#onUnloadTrans
*/
Trans.prototype.unInitFileNav = function() {
$("#fileList .fileListUl").empty();
ui.fileList.reIndex();
this.onFileNavUnloaded.call(this);
engines.handler('onUnloadTrans').apply(this, arguments);
ui.ribbonMenu.clear();
ui.disableButtons();
/**
* Triggers after a project is closed.
* @event Trans#onUnloadTrans
*/
this.trigger('onUnloadTrans')
}
/**
* Evaluate translation progress
* @param {(String|String[])} [file=All] - List of file IDs to be evaluated
* @param {Object} [progressData] - Progress data
*/
Trans.prototype.evalTranslationProgress = function(file, progressData) {
//data = data||{};
file = file||[];
var dataResult = progressData||trans.countTranslated(file)||{};
//if (typeof data[file] == 'undefined') dataResult = trans.countTranslated(file);
for (var id in dataResult) {
var fileSelector = $(".fileList [data-id="+common.escapeSelector(id)+"]");
fileSelector.find(".percent").text(Math.round(dataResult[id].percent));
fileSelector.find(".progress").css("background", "linear-gradient(to right, #3159f9 0%,#3159f9 "+dataResult[id].percent+"%,#ff0004 "+dataResult[id].percent+"%,#ff0004 100%)");
}
}
/**
* Get information of the current progress
* @param {Boolean} reset - If True, will reset the stats
* @returns {Object} Stats of the project
*/
Trans.prototype.getStats = function(reset) {
function countWords(str) {
return str.trim().split(/\s+/).length;
}
var stats = {
files : 0,
folders: 0,
progress: 0,
words:0,
characters:0,
rows:0,
rowTranslated:0,
percent:0,
organic:0
}
var fromCache = false;
if (!this.project) return stats;
if (!this.project.files) return stats;
if (!reset) {
if (this.project.stats) {
fromCache = true;
stats = this.project.stats;
}
}
for (var i in this.project.files) {
var thisObj = this.project.files[i];
if (thisObj.originalFormat == "TRANSLATOR++ GENERATED TABLE") continue;
stats.files++;
if (thisObj.progress) {
stats.rows += thisObj.progress.length;
stats.rowTranslated += thisObj.progress.translated;
}
if (fromCache) continue;
if (empty(thisObj.data)) continue;
for (var row=0; row<thisObj.data.length; row++) {
var thisRow = thisObj.data[row];
if (empty(thisRow)) continue;
if (!thisRow[this.keyColumn]) continue;
stats.characters += thisRow[this.keyColumn].length;
stats.words += countWords(thisRow[this.keyColumn]);
// calculating organic
var translator = this.cellInfo.getBestCellInfo(i, row, "t");
if (translator == "HU") stats.organic++
}
}
if (stats.rows > 0) stats.percent = (stats.rowTranslated/stats.rows) * 100;
stats.folders = $(".fileList .group-header").length - 1;
stats.organicPercent = (stats.organic/stats.rowTranslated) *100;
this.stats = stats;
return stats;
}
Trans.prototype.setFileNoteColor = function(color, files) {
files ||= this.getSelectedId();
if (Array.isArray(files) == false) files = [files];
for (let i in files) {
let obj = this.getObjectById(files[i]);
if (!color) {
if (obj.noteColor) delete obj.noteColor;
continue;
}
obj.noteColor = color;
}
this.drawFileStatus(files);
ui.evalFileNoteIcon();
}
Trans.prototype.setFileNote = function(note, files) {
files ||= this.getSelectedId();
if (Array.isArray(files) == false) files = [files];
for (let i in files) {
let obj = this.getObjectById(files[i]);
obj.note = note;
}
this.drawFileStatus(files);
ui.evalFileNoteIcon();
ui.fileList.reIndex();
}
/**
* Load comments into the grid
*/
Trans.prototype.loadComments = function() {
trans.grid.comment = trans.grid.comment||trans.grid.getPlugin('comments');
var selectedObj = trans.getSelectedObject();
if (!selectedObj) return false;
if (typeof selectedObj.comments == 'undefined') return false;
for (var row in selectedObj.comments) {
for (var col in selectedObj.comments[row]) {
//console.log("set comment on", row, col, selectedObj.comments[row][col]);
trans.grid.comment.setCommentAtCell(parseInt(row), parseInt(col), selectedObj.comments[row][col]);
}
}
}
// ===============================================================
// CONTEXT MENU
// ===============================================================
Trans.prototype.runCustomScript = async function(workspace, scriptPath, options) {
console.log("Running custom script with arguments:", arguments);
options = options || {};
var CodeRunner = require("www/js/CodeRunner.js")
var codeRunner = new CodeRunner();
var code = await common.fileGetContents(scriptPath);
if (!code) return alert(t("Error opening file :"+scriptPath))
await ui.showBusyOverlay();
await common.wait(200);
try {
var result = await codeRunner.run(code, workspace, options);
if (result) alert(result);
} catch (e) {
alert(t("Error executing :")+nwPath.basename(scriptPath)+"\n"+e.toString());
}
await ui.hideBusyOverlay();
}
Trans.prototype.updateRunScriptMenu = function() {
this.fileSelectorMenu = this.fileSelectorMenu || {};
// rowByrow
// resets menu
this.fileSelectorMenu.withSelected.items.runScript.items.forEachRowRun.items = {};
var forEachRowRunItems = this.fileSelectorMenu.withSelected.items.runScript.items.forEachRowRun.items;
var rowIteratorConfig = sys.getConfig("codeEditor/rowIterator");
if (!rowIteratorConfig) {
sys.setConfig("codeEditor/rowIterator", {quickLaunch:[]});
//sys.getConfig("codeEditor/rowIterator");
}
rowIteratorConfig ||= {}
rowIteratorConfig.quickLaunch ||= [];
for (let i=0; i<rowIteratorConfig["quickLaunch"].length; i++) {
(()=>{
var filePath = rowIteratorConfig["quickLaunch"][i];
var filename = common.getFilename(filePath);
var thisId = common.generateId()
forEachRowRunItems[thisId] = {
name: filename,
callback: (key, opt) => {
var conf = confirm(t("Are you sure want to execute the following script?")+"\n"+filename);
if (!conf) return;
this.runCustomScript("rowIterator", filePath);
}
}
})()
}
// object by object
// resets menu
this.fileSelectorMenu.withSelected.items.runScript.items.forEachObjectRun.items = {};
var forEachObjectRunItems = this.fileSelectorMenu.withSelected.items.runScript.items.forEachObjectRun.items;
var objectIteratorConfig = sys.getConfig("codeEditor/objectIterator");
if (!objectIteratorConfig) {
sys.setConfig("codeEditor/objectIterator", {quickLaunch:[]});
objectIteratorConfig = sys.getConfig("codeEditor/objectIterator");
}
objectIteratorConfig["quickLaunch"] = objectIteratorConfig["quickLaunch"] || [];
for (let i=0; i<objectIteratorConfig["quickLaunch"].length; i++) {
(()=>{
var filePath = objectIteratorConfig["quickLaunch"][i];
var filename = common.getFilename(filePath);
var thisId = common.generateId()
forEachObjectRunItems[thisId] = {
name: filename,
callback: (key, opt) => {
var conf = confirm(t("Are you sure want to execute the following script?")+"\n"+filename);
if (!conf) return;
this.runCustomScript("objectIterator", filePath);
}
}
})()
}
}
Trans.prototype.updateRunScriptGridMenu = function() {
// cell level
this.gridContextMenu.runAutomation.submenu = {
items: []
}
var forEachCellRunItems = this.gridContextMenu.runAutomation.submenu.items;
var cellSelectionConfig = sys.getConfig("codeEditor/gridSelection");
if (!cellSelectionConfig) {
sys.setConfig("codeEditor/gridSelection", {quickLaunch:[]});
cellSelectionConfig = sys.getConfig("codeEditor/gridSelection");
}
cellSelectionConfig["quickLaunch"] = cellSelectionConfig["quickLaunch"] || [];
for (var i=0; i<cellSelectionConfig["quickLaunch"].length; i++) {
(()=>{
var filePath = cellSelectionConfig["quickLaunch"][i];
console.log("Adding context menu", filePath);
var filename = common.getFilename(filePath);
var thisId = common.generateId()
forEachCellRunItems.push({
name: filename,
key:"runAutomation:"+thisId,
callback: (key, opt) => {
var conf = confirm(t("Are you sure want to execute the following script?")+"\n"+filename);
if (!conf) return;
this.runCustomScript("gridSelection", filePath);
}
})
})()
}
}
/**
* Initialize grid's context menu
*
*/
Trans.prototype.fileSelectorContextMenuInit = function() {
console.log("trans.fileSelectorContextMenuInit");
var trans = this;
this.fileSelectorMenu = {
"selectAll" : {"name" : t("Select all"), icon:"context-menu-icon icon-check2-all"},
"clearSelection" : {"name" : t("Clear selection")},
"selectCompleted" : {"name" : t("Select 100%")},
"selectIncompleted" : {"name" : t("Select <100%")},
"selectMarkedAsCompleted" : {"name" : t("Select completed")},
"invertSelection" : {"name" : t("Invert selection")},
"sep0": "---------",
"markCompleteCurrent":{
"name" : t("Toggle mark as complete"),
icon: function() {
return 'context-menu-icon icon-ok';
}
},
"sep1": "---------",
"withSelected": {
name: () => {
var checkedLength = $(".fileCheckbox:checked").length;
if (checkedLength == 0) {
trans.fileSelectorMenu.withSelected.icon = 'context-menu-icon icon-docs-1';
trans.fileSelectorMenu.withSelected.items.deleteFiles.visible = false;
return t("With all");
} else {
return t("With ") + checkedLength + t(" selected")
}
},
icon: function() {
return 'context-menu-icon icon-check';
},
items: {
"markComplete": {
name : t("Mark as complete"),
icon: 'context-menu-icon icon-ok'
},
"unsetMarkComplete": {
name : t("Un-mark as complete")
},
"sep0-0":"---------",
"batchTranslation": {
name : t("Batch translation"),
icon: 'context-menu-icon icon-language'
},
"sep0-1":"---------",
"wrapText": {"name" : t("Wrap texts")},
"trim": {"name" : t("Trim")},
"padding": {"name" : t("Auto padding")},
"createScript": {
name : t("Create Automation"),
icon: 'context-menu-icon icon-code',
items: {
"forEachObject" : {"name": t("For each object"), icon: 'context-menu-icon icon-doc'},
"forEachRow" : {"name": t("For each row"),icon: 'context-menu-icon icon-menu-1'},
}
},
"runScript": {
name : t("Run Automation"),
icon: 'context-menu-icon icon-play',
items: {
"forEachObjectRun" : {
name: t("For each object"),
icon: 'context-menu-icon icon-doc',
items: {
}
},
"forEachRowRun" : {
name: ()=> {
console.log("rendering for each row");
return t("For each row")
},
icon: 'context-menu-icon icon-menu-1',
items: {
}
},
}
},
"sep0-2":"---------",
"import": {
name:t("Import from..."),
icon:"context-menu-icon icon-login",
items: {
"importFromTrans" : {"name": t("Trans File"),icon: 'context-menu-icon icon-tpp'},
"importFromSheet" : {"name": t("Spreadsheets"),icon: 'context-menu-icon icon-file-excel'},
"importFromRPGMTransPatch" : {"name": t("RPGMTransPatch Files"),icon: 'context-menu-icon icon-doc-text'}
}
},
"export": {
name:t("Export into..."),
icon:"context-menu-icon icon-export",
items: {
"exportToGamePatch" : {"name": t("A folder"), icon:() => 'context-menu-icon icon-folder-add'},
"exportToGamePatchZip" : {"name": t("Zipped Game Patch"),icon:() => 'context-menu-icon icon-file-archive'},
"exportToCsv" : {"name": t("Comma Separated Value (csv)"),icon:() => 'context-menu-icon icon-file-excel'},
"exportToXlsx" : {"name": t("Excel 2007 Spreadsheets (xlsx)"),icon:() => 'context-menu-icon icon-file-excel'},
"exportToXls" : {"name": t("Excel Spreadsheets (xls)"),icon:() => 'context-menu-icon icon-file-excel'},
"exportToOds" : {"name": t("ODS Spreadsheets"),icon:() => 'context-menu-icon icon-file-excel'},
"exportToHtml" : {"name": t("Html Spreadsheets"),icon:() => 'context-menu-icon icon-file-code'},
"exportToTransPatch" : {"name": t("RMTrans Patch"),icon:() => 'context-menu-icon icon-doc-text'}
}
},
"inject" : {
name: t("Inject Translation"),
icon: "context-menu-icon icon-download"
},
"revert" : {
name: t("Revert to original"),
icon: "context-menu-icon icon-ccw"
},
"sep0-3":"---------",
"clearTranslationSel": {"name" : t("Clear translation"), "icon":"context-menu-icon icon-eraser"},
"deleteFiles": {"name" : t("Delete files"), "icon":"context-menu-icon icon-trash-empty"},
}
},
"sep2": "---------",
"properties": {
name: t("Properties"),
icon:'context-menu-icon icon-cog'
}
}
if (trans.fileSelectorContextMenuIsInitialized) return false;
$.contextMenu({
selector: '.fileList .data-selector',
events: {
preShow : function($target, e) {
//$(".context-menu-root").trigger("contextmenu:hide")
/*
console.log(arguments);
var $cTarget = $target;
$cTarget.closest("ul").find(".contextMenuOpened").removeClass("contextMenuOpened");
$cTarget.addClass("contextMenuOpened");
*/
//console.log(arguments);
},
hide : function($target, e){
//$(".fileList .data-selector.contextMenuOpened").removeClass("contextMenuOpened");
}
},
build: function($triggerElement, e) {
var thisCallback = function(key, options) {
switch (key) {
case "selectAll" :
trans.selectAll();
break;
case "clearSelection" :
trans.clearSelection();
break;
case "invertSelection" :
trans.invertSelection();
break;
case "selectCompleted" :
trans.selectAll(trans.getAllCompletedFiles())
break;
case "selectIncompleted" :
trans.selectAll(trans.getAllIncompletedFiles())
break;
case "selectMarkedAsCompleted" :
trans.selectAll(trans.getAllMarkedAsCompleted());
break;
case "markCompleteCurrent" :
var $elm = $("#fileList .context-menu-active");
var action = !$elm.hasClass("isCompleted");
var currentFile = $elm.data("id");
trans.setMarkAsComplete(action, [currentFile]);
break;
case "markComplete" :
trans.setMarkAsComplete(true);
break;
case "unsetMarkComplete" :
trans.setMarkAsComplete(false);
break;
case "batchTranslation" :
ui.translateAllDialog();
break;
case "forEachObject" :
ui.openAutomationEditor("codeEditor_objectIterator", {
workspace: "objectIterator"
});
break;
case "forEachRow" :
ui.openAutomationEditor("codeEditor_rowIterator", {
workspace: "rowIterator"
});
break;
case "clearTranslationSel" :
var confirmation = confirm(t("Do you want to clear translation?"));
var selection = trans.getCheckedFiles();
if (confirmation) trans.removeAllTranslation(trans.getCheckedFiles(), {refreshGrid:true});
trans.evalTranslationProgress(selection);
break;
case "clearTranslationAll" :
var confirmation2 = confirm(t("Do you want to clear translation?"));
var selection2 = trans.getAllFiles();
if (confirmation2) trans.removeAllTranslation(trans.getAllFiles(), {refreshGrid:true});
trans.evalTranslationProgress(selection2);
break;
case "deleteFiles" :
ui.deleteFiles();
break;
case "wrapText" :
ui.batchWrapingDialog();
break;
case "trim" :
ui.openTrimWindow();
break;
case "padding" :
ui.openPaddingWindow();
break;
case "properties" :
ui.openFileProperties();
break;
// imports
case "importFromSheet":
ui.openImportSpreadsheetDialog();
break;
case "importFromTrans":
$("#importTrans").trigger("click");
break;
case "importFromRPGMTransPatch":
ui.openImportRPGMTransDialog();
break;
// exports
case "exportToGamePatch":
$("#dialogExport").data("options", {files:trans.getCheckedFiles()});
$("#exportDir").trigger("click");
break;
case "exportToGamePatchZip":
$("#dialogExport").data("options", {files:trans.getCheckedFiles()});
$("#export").trigger("click");
break;
case "exportToCsv":
$("#exportCSV").trigger("click");
break;
case "exportToXlsx":
$("#dialogExport").data("options", {files:trans.getCheckedFiles()});
$("#exportXLSX").trigger("click");
break;
case "exportToXls":
$("#dialogExport").data("options", {files:trans.getCheckedFiles()});
$("#exportXLS").trigger("click");
break;
case "exportToOds":
$("#dialogExport").data("options", {files:trans.getCheckedFiles()});
$("#exportODS").trigger("click");
break;
case "exportToHtml":
$("#dialogExport").data("options", {files:trans.getCheckedFiles()});
$("#exportHTML").trigger("click");
break;
case "exportToTransPatch":
$("#dialogExport").data("options", {files:trans.getCheckedFiles()});
$("#exportTrans").trigger("click");
break;
case "inject":
ui.openInjectDialog();
break;
case "revert":
trans.revertToOriginal();
break;
default :
}
}
trans.updateRunScriptMenu()
return {
zIndex :1000,
callback: thisCallback,
items : trans.fileSelectorMenu
}
}
});
trans.fileSelectorContextMenuIsInitialized = true;
}
/**
* Initialize grid's body context menu
*/
Trans.prototype.gridBodyContextMenu = function() {
$.contextMenu({
selector: '.ht_master .htCore tbody, .ht_clone_left .htCore tbody',
events: {
preShow : function($target, e) {
//$(".context-menu-root").trigger("contextmenu:hide")
var cTarget = $(e.target);
console.log(cTarget);
if (cTarget.hasClass("highlight")) {
console.log("previously hightlighted");
}
console.log(arguments);
},
show : function($target, e){
console.log("selection on show : ");
trans.grid.lastContextMenuCellRange = trans.grid.getSelectedRange();
},
hide : function($target, e){
console.log("reload selection : ");
if (typeof trans.grid.lastContextMenuCellRange == "undefined") return false;
trans.grid.selectCells(trans.grid.lastContextMenuCellRange);
console.log(trans.grid.getSelectedRange());
}
},
build: function($triggerElement, e) {
var thisCallback = function(key, options) {
switch (key) {
case "addComment" :
var thisCoord= undefined;
try {
thisCoord = trans.grid.lastContextMenuCellRange[0]['highlight']
} catch (error) {
// do nothing
}
trans.editNoteAtCell(thisCoord);
break;
case "removeComment" :
trans.removeNoteAtSelected(trans.grid.lastContextMenuCellRange);
break;
default :
}
}
return {
zIndex:1000,
callback: thisCallback,
items: {
"addComment": {name: t("Add comment"), icon: function(){
return 'context-menu-icon icon-commenting-o';
}},
"removeComment": {name: t("Remove comment"), icon: function(){
return 'context-menu-icon icon-comment-empty';
}},
"selectAll" : {"name" : t("Select all")},
"invertSelection" : {"name" : t("Invert selection")},
"sep0": "---------",
"withSelected": {
name: "With all",
items: {
"batchTranslation": {"name" : t("Batch translation")},
"wordWrap": {"name" : t("Wrap texts")}
}
}
}
}
}
});
}
//================================================================
//
// CONTEXT RELATED
//
//================================================================
/**
* Sanitize query for contexts
* @param {...String|...Array} - Context
* @returns {String[]}
*/
Trans.prototype.evalContextsQuery = function() {
if (arguments.length == 0) return false;
var result = [];
for (var i=0; i<arguments.length; i++) {
if (typeof arguments[i] == "string") {
if (arguments[i].length == 0) continue;
var thisA = arguments[i].split("\n").map(function(input) {
return common.stripCarriageReturn(input);
});
result = result.concat(thisA);
} else if (Array.isArray(arguments[i])) {
if (arguments[i].length == 0) continue;
result = result.concat(arguments[i]);
}
}
return result;
}
/**
* Check whether the a row has the context or not
* @param {String} file - The file ID
* @param {Number} row - The row to look for
* @param {String[]} context - The context to check for
* @returns {Boolean}
*/
Trans.prototype.isInContext = function(file, row, context) {
context = context||[];
if (context.length == 0) return true;
if (typeof context == 'string') context = [context];
if (typeof trans.project.files[file] == 'undefined') return false;
if (typeof trans.project.files[file].context[row] == 'undefined') return false;
var thisContextS = trans.project.files[file].context[row];
if (Array.isArray(thisContextS) == false) thisContextS = [thisContextS];
if (thisContextS.length < 1) {
// try to findout on parameters
if (typeof trans.project.files[file].parameters[row] != 'undefined') {
thisContextS = [trans.buildContextFromParameter(trans.project.files[file].parameters[row])];
}
//continue;
}
var contextStr = thisContextS.join("\n");
for (var i=0; i<context.length; i++) {
contextStr = contextStr.toLowerCase();
if (contextStr.indexOf(context[i].toLowerCase()) != -1) return true;
}
return false;
}
/**
* Deletes rows by context
* @param {String} files
* @param {String[]} contexts
* @param {Object} options
* @param {Boolean} whitelist
*/
Trans.prototype.removeRowByContext = function(files, contexts, options, whitelist) {
/*
improved by. Vellithe
*/
options=options||{};
options.matchAll = options.matchAll||false;
var collection = trans.travelContext(files, contexts, {
onMatch:function(file, row) {
//trans.removeRow(file, row);
//console.log("removing "+files+" row "+row);
},
matchAll:options.matchAll
});
for (var file in collection) {
for (var row=collection[file].length-1; row>=0; row--) {
if ((collection[file][row] == true && whitelist !== true) || (collection[file][row] != true && whitelist === true)) {
console.log("removing "+file+" row "+row + (whitelist === true ? " (Not on whitelist)" : ""));
trans.removeRow(file, row);
//trans.project.files[file].data.splice(row, 1);
}
}
}
trans.refreshGrid();
}
/**
* Generates context's keywords
* @param {Object} [obj=trans.project] - Trans.project object
* @param {String[]} [files] - List of the files
* @param {Object} [options] - Options
* @returns {Object} Collection of the keywords
*/
Trans.prototype.collectContextKeyword = function(obj, files, options) {
files = files||[];
obj = obj||trans.project;
if (typeof obj == 'undefined') return false;
if (typeof files == "string") files = [files];
if (files.length < 1) { // select all
for (let file in trans.project.files) {
files.push(file);
}
}
//console.log(files);
var collection = {};
for (let i=0; i<files.length; i++) {
let file = files[i];
var thisData = obj.files[file].context;
for (var contextId=0; contextId<thisData.length; contextId++) {
if (Array.isArray(thisData[contextId]) == false) continue;
for (var y=0; y<thisData[contextId].length; y++) {
var contextString = thisData[contextId][y]||"";
var contextPart = contextString.split("/");
for (var x=0; x<contextPart.length; x++) {
if (isNaN(contextPart[x])) {
collection[contextPart[x]] = collection[contextPart[x]]||0;
collection[contextPart[x]] += 1;
}
}
}
}
}
return collection;
}
/**
* Iterate through contexts
* @param {(String|String[])} files - Selected files
* @param {(String|String[])} contexts - Context to search for
* @param {Object} options
* @param {Function} options.onMatch
* @param {Function} options.onNotMatch
* @param {Boolean} options.matchAll
*/
Trans.prototype.travelContext = function(files, contexts, options) {
//remove related context
files = files||[];
contexts = contexts||[]; // keywords
options = options||{};
options.onMatch = options.onMatch||function(){};
options.onNotMatch = options.onNotMatch||function(){};
options.matchAll = options.matchAll||false;
if (typeof files == "string") files = [files];
if (Array.isArray(contexts) == false) contexts = [contexts];
if (files.length < 1) { // select all
for (let file in trans.project.files) {
files.push(file);
}
}
//console.log(files);
var collection = {};
for (let i=0; i<files.length; i++) {
let file = files[i];
collection[file] = [];
for (let rowId=0; rowId<trans.project.files[file].context.length; rowId++) {
var thisContextS = trans.project.files[file].context[rowId];
if (Array.isArray(thisContextS) == false) thisContextS = [thisContextS];
collection[file][rowId] = false;
if (thisContextS.length < 1) {
// try to findout on parameters
if (!trans.project.files[file].parameters[rowId]) continue;
thisContextS = [trans.buildContextFromParameter(trans.project.files[file].parameters[rowId])];
//continue;
}
for (var y=0; y<thisContextS.length; y++) {
var thisContext = thisContextS[y];
//console.log(thisContext);
for (var x=0; x<contexts.length; x++) {
//try {
if (options.matchAll) {
if (common.matchAllWords(thisContext, contexts[x])) {
collection[file][rowId] = true;
}
} else {
//console.log("comparing "+thisContext+" with "+contexts[x]);
if (thisContext.toLowerCase().indexOf(contexts[x].toLowerCase()) >= 0) {
//console.log("match");
//matchFound = true;
//if (options.onMatch.call(trans.project.files[file], file, rowId) === false) return false;
collection[file][rowId] = true;
//break;
} else {
//if (options.onNotMatch.call(trans.project.files[file], file, rowId) === false) return false;
}
}
//} catch(err) {
//}
}
}
}
for (let rowId=0; rowId<collection[file].length; rowId++) {
if (collection[file][rowId] == true) {
options.onMatch.call(trans.project.files[file], file, rowId);
} else {
options.onNotMatch.call(trans.project.files[file], file, rowId);
}
}
}
return collection;
}
//================================================================
//
// UTILITY
//
//================================================================
Trans.prototype.isOrganicCell = function(row, col, file) {
if (!this.project) return false;
file ||= this.getSelectedId();
var cellInfo = this.cellInfo.getCell(file, row, col);
if (cellInfo?.t == "HU") return true;
return false;
}
Trans.prototype.isVisitedCell = function(row, col, file) {
if (!this.project) return false;
file ||= this.getSelectedId();
return Boolean(this.cellInfo.get("v", file, row, col));
}
/**
* Get data from the file object
* @param {(Object|String|undefined)} file - File ID or File Object or undefined
* @returns {String[][]} The array representation of the table
*/
Trans.prototype.getData = function(file) {
if (typeof file == 'undefined') return this.getCurrentData();
if (typeof file == "string") return this.getObjectById(file).data;
if (typeof file == 'object') {
if (Array.isArray(file.data)) return file.data;
}
console.warn("invalid id or object ", file);
return [];
}
/**
* Add a new key into a data
* @param {Any} file - File ID or File Object or undefined
* @param {String} keyString - keyString
* @param {String} defaultTranslation - Default translation
* @returns {Integer} the index of the new inserted data
*/
Trans.prototype.addRow = function(file, keyString, defaultTranslation) {
var thisObj
if (typeof file == "object") {
thisObj = file;
} else {
file = file || this.getSelectedId();
thisObj = this.getObjectById(file);
}
thisObj.indexIds ||= {};
if (typeof keyString !== "string") return -1;
if (!keyString) return -1;
const existingIndex = this.getIndexByKey(thisObj, keyString);
console.log("Existing index is", existingIndex);
if (typeof existingIndex !== "undefined") return existingIndex;
var newRow = Array(trans.colHeaders.length).fill(null);
newRow[this.keyColumn] = keyString;
if (defaultTranslation) newRow[1] = defaultTranslation;
console.log("inserting row:", newRow);
var theData = trans.getData(thisObj);
console.log("theData", theData)
if (empty(theData[theData.length - 1][0])) {
// overwrite the last empty cell
let thisIndex = theData.length - 1
theData[thisIndex] = newRow;
thisObj.indexIds[keyString] = thisIndex;
return thisIndex;
} else {
var newKey = theData.push(newRow);
thisObj.indexIds[keyString] = newKey -1;
return newKey;
}
}
/**
* Get indexes of a fileId
* @param {String|Object} [file=this.getSelectedId()] - File id or file object to be indexed
* @returns {Object} Key-Value pair of the key text and its row index
*/
Trans.prototype.getIndexIds = function(file) {
if (typeof file == 'undefined') file = this.getSelectedId();
var theObject;
if (typeof file == "string") {
theObject = this.getObjectById(file);
if (!theObject) return {}
if (!theObject.indexIsBuilt) this.buildIndex(file);
return theObject.indexIds;
} else if (typeof file == 'object') {
theObject = file;
if (!theObject.indexIsBuilt) theObject = this.buildIndexFromData(theObject);
return theObject.indexIds;
}
console.warn("Error getting Index ID for file:", file);
return {};
}
/**
* Get row index based on the key string
* @param {String|Object} file - File ID or File Object to be searched
* @param {String} keyString - Key string to look for
* @returns {Number|undefined} - Row id of the keyString if exist. Undefined if not exist.
*/
Trans.prototype.getIndexByKey = function(file, keyString) {
return this.getIndexIds(file)[keyString];
}
/**
* Check whether all available files are checked
* @returns {Boolean} True if all files are checked
*/
Trans.prototype.isAllSelected = function() {
if (trans.getCheckedFiles().length == Object.keys(trans.project.files).length) return true;
return false;
}
/**
* Get the current active translator engine's ID
* @returns {String} Active translator ID
*/
Trans.prototype.getActiveTranslator = function() {
if (!trans.project) return sys.config?.translator;
trans.project.options = trans?.project.options || {};
return trans.project?.options?.translator || sys.config.translator;
}
/**
* Append text to the common reference
* @param {String} text - Key text to append
* @returns {Boolean} Return false if failed
*/
Trans.prototype.appendTextToReference = function(text) {
if (typeof text !== 'string') return false;
if (Boolean(text)==false) return false;
if (trans.isKeyExistOn(text, "Common Reference")) return trans.alert("Unable to add <b>"+text+"</b>. That value already exist on Common Reference!");
var ref= trans.project.files["Common Reference"];
var lastKey = ref.data.length-1;
if (Boolean(ref.data[lastKey][0]) == false) {
console.log("inserting to ref.data[lastKey][0]");
ref.data[lastKey][0] = text;
ref.indexIds[text] = lastKey;
} else {
console.log("append new data");
var newData = new Array(trans.colHeaders.length);
newData = newData.fill(null);
newData[0] = text;
ref.data.push(newData);
ref.indexIds[text] = ref.data.length-1;
}
trans.alert("<b>"+text+"</b> "+t("added to reference table!"));
return true;
}
/**
* Word wrap a file object
* @param {String[]} [files=this.getAllFiles()] - List of files to be processed
* @param {Number} [col=1] - Column ID to be processed
* @param {Number} [targetCol=col+1] - Column of the processed text will put into
* @param {Object} [options]
* @param {Number} [options.maxLength=41] - Maximum length of the line
* @param {String[]} [options.context] - Context filter. Only the rows that has this context will be processed
* @param {Function} [options.onDone] - Triggered when the process is completed
*/
Trans.prototype.wordWrapFiles = function(files, col, targetCol, options) {
files = files||[];
if (typeof files == 'string') files = [files];
if (files.length == 0 ) files = this.getAllFiles();
//console.log(arguments);
//return true;
col = col||1;
targetCol = targetCol||col+1;
if (targetCol == 0) return trans.alert(t("Can not modify Column 0"));
options = options||{};
options.maxLength = options.maxLength||41; // default with picture, without picture is 50
options.onDone = options.onDone||function() {};
options.context = options.context||[] // context filter
for (var id=0; id<files.length; id++) {
var file = files[id];
console.log("Wordwrapping file : "+file);
if (typeof trans.project.files[file] == 'undefined') continue;
options.lineBreak = options.lineBreak||trans.project.files[file].lineBreak||"\n";
var thisData = trans.project.files[file].data;
//console.log(thisData);
for (var row=0; row<thisData.length; row++) {
if (!trans.isInContext(file, row, options.context)) continue;
if (typeof thisData[row][col] !== 'string') {
thisData[row][targetCol] = thisData[row][col];
}
thisData[row][targetCol] = common.wordwrapLocale(thisData[row][col], options.maxLength, this.getTl(), options.lineBreak);
}
}
options.onDone.call(trans);
}
/**
* Fill empty lines from other column
* @param {String|String[]} files - File ID or list of file ID
* @param {Number[]} rows - Row ID or list of row ID
* @param {Number} targetCol - Target column
* @param {Number} sourceCol
* @param {Object} options
* @param {Object} [options.project=trans.project]
* @param {Object} [options.keyColumn=0]
* @param {Function} [options.lineFilter]
* @param {Boolean} [options.fromKeyOnly=false]
* @param {String[]} [options.filterTag]
* @param {Boolean} [options.overwrite=false]
*/
Trans.prototype.fillEmptyLine = function(files, rows, targetCol, sourceCol, options) {
/*
Integer targetCol
Integer sourceCol
*/
// if targetCol is undefined, than the right most row with existed translation will be picked
files = files||[];
if (typeof files == 'string') files = [files];
options = options||{};
options.project = options.project||trans.project;
options.keyColumn = options.keyColumn||0;
options.lineFilter = options.lineFilter|| function() {return true};
options.fromKeyOnly = options.fromKeyOnly || false; // fill from key column only
options.filterTag = options.filterTag || [];
options.overwrite = options.overwrite || false;
if (options.fromKeyOnly) {
console.warn("collecting data from key only");
options.sourceCol = sourceCol||options.keyColumn;
}
rows = rows||[];
if (typeof rows == 'number') rows = [rows];
if (files.length == 0) files = trans.getAllFiles();
console.log(files);
for (var index=0; index<files.length; index++) {
let file = files[index];
//console.log(file);
//var thisLineBreak = options.project.files[file].thisLineBreak||"\n";
if (rows.length > 0) {
// do nothing
} else { // all row
var thisData = options.project.files[file].data;
for (var row=0; row<thisData.length; row++) {
if (options.filterTagMode == "blacklist") {
if (this.hasTags(options.filterTag, row, file)) continue;
} else if (options.filterTagMode == "whitelist") {
if (!this.hasTags(options.filterTag, row, file)) continue;
}
if (typeof targetCol == 'undefined') {
targetCol = trans.getTranslationColFromRow(thisData[row]);
if (targetCol == null) continue; // no translation exist
}
if (options.overwrite == false) {
if (thisData[row][targetCol]) continue;
}
/*
if (typeof sourceCol == 'undefined') {
sourceCol = trans.getTranslationColFromRow(thisData[row], targetCol); // get translation except targetCol
if (sourceCol == null) continue; // no source found
}
*/
options.project.files[file].data[row][targetCol] = trans.getTranslationByLine(thisData[row], options.keyColumn, {
includeIndex :true,
priorityCol :targetCol,
onBeforeLineAdd :options.lineFilter,
sourceCol :options.sourceCol//column to check, undefined means all
});
}
}
}
}
/**
* remove whitespace from translation
* @param {(String|String[])} files - File id(s) to process
* @param {(Number|Number[])} columns - column to process
* @param {Object} options
* @param {Boolean} options.refreshGrid - Refresh the current grid after process is completed
*/
Trans.prototype.trimTranslation = function(files, columns, options) {
// remove whitespace from translation
if (!trans.project) return false;
files = files||trans.getSelectedId();
options = options||{};
options.refreshGrid = options.refreshGrid||false;
if (Array.isArray(files) == false) files = [files];
if (Array.isArray(columns) == false) columns = [columns];
for (var i=0; i<files.length; i++) {
var file = files[i];
//console.log("handling "+file);
var thisData = trans.project.files[file].data;
//var thisLineBreak = trans.project.files[file].lineBreak||"\n";
var originalLineBreak = trans.project.files[file].lineBreak||"\n";
var thisLineBreak = "\n";
for (var row=0; row<thisData.length; row++) {
//console.log("handling row "+row);
for (var colID in columns) {
var col = columns[colID];
//console.log("handling col "+col);
//console.log(trans.project.files[file].data[row][col]);
if (col < 1) continue;
if (typeof trans.project.files[file].data[row][col] !== 'string') continue;
var lines = trans.project.files[file].data[row][col].split(thisLineBreak);
var newLines = lines.map(function(thisVal) {
//console.log(thisVal.trim());
return thisVal.trim();
});
trans.project.files[file].data[row][col] = newLines.join(originalLineBreak);
}
}
}
//if (options.refreshGrid) {
trans.refreshGrid();
//}
}
/**
* Copy left padding of the key texts into translations
* @param {(String|String[])} files - File id(s) to process
* @param {(Number|Number[])} columns - column to process
* @param {Object} options
* @param {Number} [options.keyId=0] - The index of the key column
* @param {Boolean} options.includeInitialWhitespace
* @param {Boolean} options.refreshGrid - Refresh the current grid after process is completed
*/
Trans.prototype.paddingTranslation = function(files, columns, options) {
// Copy left padding from keys to translations
if (!trans.project) return false;
files = files||trans.getSelectedId();
options = options||{};
options.keyId = options.keyId||0;
options.includeInitialWhitespace = options.includeInitialWhitespace||false;
options.refreshGrid = options.refreshGrid||false;
if (Array.isArray(files) == false) files = [files];
if (Array.isArray(columns) == false) columns = [columns];
//var whiteSpaces = /^[ \s\u00A0\f\n\r\t\v\u00A0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u2028\u2029\u202f\u205f\u3000]+/g
//var whiteSpaces = /^[\r\n\t\f\v \u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\u0009\u200b\u180e\u2060]+/g;
var whiteSpaces = /^[\s\u0009\u200b\u180e\u2060]+/g
for (var i=0; i<files.length; i++) {
var file = files[i];
var thisData = trans.project.files[file].data;
var thisLineBreak = trans.project.files[file].lineBreak||"\n";
for (var row=0; row<thisData.length; row++) {
if (typeof trans.project.files[file].data[row][options.keyId] !== 'string') continue;
var keys = trans.project.files[file].data[row][options.keyId].split(thisLineBreak);
var leftWhiteSpaces = [];
for (var keysID=0; keysID<keys.length; keysID++) {
var thisLeftWS = keys[keysID].match(whiteSpaces);
if (Boolean(thisLeftWS) == false) thisLeftWS = "";
leftWhiteSpaces.push(thisLeftWS);
}
for (var colID in columns) {
var col = columns[colID];
if (col < 1) continue;
if (typeof trans.project.files[file].data[row][col] !== 'string') continue;
var lines = trans.project.files[file].data[row][col].split(thisLineBreak);
var newLines = [];
for (var linePartId=0; linePartId<lines.length; linePartId++) {
var thisWhitespace = leftWhiteSpaces[linePartId]||"";
if (options.includeInitialWhitespace) {
newLines.push(thisWhitespace+lines[linePartId]);
} else {
newLines.push(thisWhitespace+lines[linePartId].trim());
}
}
trans.project.files[file].data[row][col] = newLines.join(thisLineBreak);
}
}
}
//if (options.refreshGrid) {
trans.refreshGrid();
//}
}
/**
* Clear translations from selected files
* @param {(String|String[])} files - File id(s)
* @param {Object} options
* @param {Boolean} options.refreshGrid - Refresh the current grid after process is completed
*/
Trans.prototype.removeAllTranslation = function(files, options) {
if (!trans.project) return false;
files = files||trans.getSelectedId();
options = options||{};
options.refreshGrid = options.refreshGrid||false;
if (Array.isArray(files) == false) files = [files];
for (var i=0; i<files.length; i++) {
var file = files[i];
var thisData = trans.project.files[file].data;
for (var row=0; row<thisData.length; row++) {
for (var col=1; col<thisData[row].length; col++) {
trans.project.files[file].data[row][col] = null;
}
}
}
/**
* Triggered when all all translation are removed
* @event Trans#removeAllTranslation
* @param {Object} Options
* @param {String[]} Options.files - List of the file ids
* @param {Object} Options.options - Options
*/
this.trigger("removeAllTranslation", {files:files, options:options});
if (options.refreshGrid) {
trans.refreshGrid();
}
}
/**
* Delete files
* @param {(String|String[])} files - Files to be deleted
* @param {Object} options
* @returns {Boolean} True on success
*/
Trans.prototype.deleteFile = function(files, options) {
if (!trans.project) return false;
files = files||trans.getSelectedId();
options = options||{};
if (Array.isArray(files) == false) files = [files];
if (files.length < 1) return true;
for (var i=0; i<files.length; i++) {
// unselect if selected
var file = files[i];
if (trans.project.files[file].type == 'reference') {
alert(t("Unable to delete table : ")+file);
continue;
}
if (file == trans.getSelectedId()) {
ui.disableGrid(true);
}
var bak = JSON.parse(JSON.stringify(trans.project.files[file]));
trans.project.trash = trans.project.trash||{};
trans.project.trash[file] = bak;
$(".panel-left .fileList [data-id="+common.escapeSelector(file)+"]").remove();
delete trans.project.files[file];
}
ui.fileList.reIndex();
this.trigger("afterDeleteFile", [files]);
}
/**
* Remove one or more files from staging directory
* This function is useful for cleaning up staging files for example after removing the project's object
* @async
* @param {String[]} files - List of files to be removed
* @since 4.4.4
*/
Trans.prototype.stagingFilesRemove = async function(files) {
if (!Array.isArray(files)) files = [files];
var stagingPath = this.getStagingPath();
if (!stagingPath) return [];
var result = []
for (var i in files) {
var fileToRemove = nwPath.join(stagingPath, "data", files[i]);
console.log("Removing", fileToRemove);
if (!await common.isFileAsync(fileToRemove)) console.log("Not found:", fileToRemove);
result.push(await common.unlink(files[i]));
}
return result;
}
/**
* Removes row
* @param {String} file - File to be processed
* @param {(Number|Number[])} rows - Rows to be removed
* @param {Object} options
* @param {Boolean} options.permanent - will put into the temporary bucket when false
* @param {Boolean} options.refreshGrid - Refresh the current grid after process is completed
*/
Trans.prototype.removeRow = function(file, rows, options) {
console.log("removing row : ", arguments);
if (typeof file == 'undefined') return false;
if (typeof rows == 'undefined') return false;
if (rows === 0) rows = [0];
rows = rows||[];
options = options||{};
if (Array.isArray(rows) == false) rows = [rows];
options.permanent = options.permanent||false;
options.refreshGrid = options.refreshGrid||false;
// make the array unique, so one row can be only removed once
rows = common.arrayUnique(rows);
// sort array descending! this is important!
rows.sort(function(a, b) {
return b - a;
});
console.log("Removing rows > should be ordered descendingly:", rows);
for (var i=0; i<rows.length; i++) {
var thisRow = rows[i];
if (typeof trans.project.files[file].data[thisRow] == 'undefined') continue;
trans.project.files[file].data.splice(thisRow, 1);
if (trans.project.files[file].parameters) trans.project.files[file].parameters.splice(thisRow, 1);
if (trans.project.files[file].context) trans.project.files[file].context.splice(thisRow, 1);
if (trans.project.files[file].tags) trans.project.files[file].tags.splice(thisRow, 1);
// adjust cellInfo
this.cellInfo.deleteRow(file, thisRow);
// adjust comment
var comments = this.getObjectById(file).comments;
if (empty(comments)) continue;
if (Array.isArray(comments)) {
comments.splice(thisRow, 1);
} else {
delete comments[thisRow];
}
}
if (rows.length > 0) trans.project.files[file].indexIsBuilt = false;
/**
* Triggered after removing rows
* @event Trans#afterRemoveRow
* @param {Object} options
* @param {String} options.file - file id
* @param {Number[]} options.rows - List of rows
* @param {Object} options.options
*/
this.trigger("afterRemoveRow", {file:file, rows:rows, options:options});
if (options.refreshGrid) {
trans.refreshGrid();
}
}
/**
* Clear translations from rows
* @since 4.11.30
* @param {Object|String} file - File ID or file object
* @param {Number|Number[]} rows - A row or array of rows.
* @param {Object} [options] - Options
* @returns {Number} - Affected row[s]
*/
Trans.prototype.clearRow = function(file, rows, options={}) {
if (typeof file == "string") file = trans.getObjectById(file);
if (!file) return;
if (!(typeof file == "object" && Array.isArray(file.data))) return console.warn("Invalid argument 1");
if (!Array.isArray(rows)) rows = [rows];
options ||= {};
var affectedRows = 0;
for (var rowId=0; rowId<rows.length; rowId++) {
var row = file.data[rows[rowId]];
for (var colId=0; colId<row.length; colId++) {
if (colId == this.keyColumn) continue;
row[colId] = "";
affectedRows++;
}
}
return affectedRows;
}
/**
* Removes a column
* This will affect the entire files
* @param {Number} column - Column to remove
* @param {Object} options
* @param {Boolean} options.refreshGrid - Refresh the current grid after process is completed
*/
Trans.prototype.removeColumn = function(column, options) {
if (column === 0) return trans.alert(t("Can not remove key column!"));
options = options||{};
options.permanent = options.permanent||false;
options.refreshGrid = options.refreshGrid||false;
if(typeof trans.project == "undefined") return trans.alert(t("Please open or create a project first"));
for (var file in trans.project.files) {
if(Array.isArray(trans.project.files[file].data) == false) continue;
if(trans.project.files[file].data.length == 0) continue;
for (var row=0; row< trans.project.files[file].data.length; row++) {
trans.project.files[file].data[row].splice(column, 1);
// adjust cellInfo
this.cellInfo.deleteCell(file, row, column);
}
}
trans.colHeaders.splice(column, 1);
trans.columns.splice(column, 1);
if (options.refreshGrid) {
trans.refreshGrid();
}
}
/**
* Rename a column
* @param {Number} column
* @param {String} newName
* @param {Object} options
* @param {Boolean} options.refreshGrid - Refresh the current grid after process is completed
*/
Trans.prototype.renameColumn = function(column, newName, options) {
if (column === 0) return trans.alert(t("Can not set column name to blank!"));
options = options||{};
options.refreshGrid = options.refreshGrid||false;
if (typeof trans.colHeaders[column] == 'undefined') return false;
trans.colHeaders[column] = newName;
if (options.refreshGrid) {
trans.refreshGrid();
}
}
/**
* Check whether a row has multiple context
* @param {Number} row - Row to check
* @param {Object} [obj=trans.getSelectedObject()] - Active object
* @returns {Boolean} True if the row has more than one context
* @since 4.10.18
*/
Trans.prototype.rowHasMultipleContext = function(row, obj) {
obj = obj || this.getSelectedObject();
if (!row) false;
if (!obj.context) return false;
if (!obj.context[row]) return false;
if (obj.context[row].length <= 1) return false;
return true;
}
/**
* Check whether the row is translated or not
* @param {Number} row - Index of the row
* @param {String[][]} data - Two dimensional array represents the table
* @returns {Boolean} True if the row has translation
*/
Trans.prototype.isTranslatedRow = function(row, data) {
data = data||trans.data;
for (var col=1; col < data[row].length; col++) {
var thisCell = data[row][col]||"";
if (thisCell.length > 0) return true;
}
return false;
}
/**
* Count how many cells are translated on the selected row
* Row index are not counted
* @param {Number} row - Row index
* @param {String[][]} data - Two dimensional array represents the table
* @returns {Number} The number of the translated cells
*/
Trans.prototype.countFilledCol = function(row, data) {
// exclude col index 0
data = data||trans.data;
var result = 0;
for (var col=1; col < data[row].length; col++) {
var thisCell = data[row][col]||"";
if (thisCell.length > 0) result++;
}
return result;
}
/**
* Retrieve the best translation in an array with Translator++'s rule
* The rightmost cell got the priority
* @param {String[]} row - Array of text
* @param {Number} [keyColumn=0] - Index of the key column
* @returns {String}
*/
Trans.prototype.getTranslationFromRow = function(row, keyColumn, skipRows=[]) {
// retrieve best translation in an array
//console.log("Get translation from row", arguments);
skipRows ||= []
if (Array.isArray(row) == false) return false;
keyColumn = keyColumn||0;
if (keyColumn == 0) {
for (let n=row.length; n>0; n--) {
if (skipRows.includes(n)) continue;
if (row[n]) {
return row[n];
}
}
} else {
for (let n=row.length; n>=0; n--) {
if (n == keyColumn) continue;
if (skipRows.includes(n)) continue;
if (row[n]) {
return row[n];
}
}
}
return null;
}
/**
* Retrieve the best translation in an array with Translator++'s rule
* The rightmost cell got the priority
* @param {String[]} row - Array of text
* @param {Number} [keyColumn=0] - Index of the key column
* @returns {Number} Cell index of the translation
*/
Trans.prototype.getTranslationColFromRow = function(row, keyColumn) {
if (Array.isArray(row) == false) return false;
keyColumn = keyColumn||0;
if (keyColumn == 0) {
for (let n=row.length; n>keyColumn; n--) {
if (row[n]) {
return n;
}
}
} else {
for (let n=row.length; n>=0; n--) {
if (n == keyColumn) continue;
if (row[n]) {
return n;
}
}
}
return null;
}
/**
* Retrieve the best translation in an array with Translator++'s rule
* The rightmost cell got the priority
* Used in line-by-line translation
* @param {String[]} row - Array of text
* @param {Number} [keyColumn=0] - Index of the key column
* @param {Object} [options]
* @param {Object} [options.includeIndex]
* @param {String} [options.lineBreak=\n] - Line break character
* @param {Function} [options.onBeforeLineAdd]
* @returns {String} The best translation
*/
Trans.prototype.getTranslationByLine = function(row, keyColumn, options) {
// get line by line best translation
if (Array.isArray(row) == false) return false;
//console.log(arguments);
keyColumn = 0;
options = options||{};
options.includeIndex = options.includeIndex||false;
options.lineBreak = options.lineBreak||"\n";
options.onBeforeLineAdd = options.onBeforeLineAdd||function() {return true};
//options.priorityCol = options.priorityCol||undefined;
//options.sourceCol = options.sourceCol||undefined;
var resultArray = [];
if (typeof options.sourceCol != 'undefined') {
let thisCell = row[options.sourceCol]||"";
let thisCellPart = thisCell.split("\n").map(function(input){
return common.stripCarriageReturn(input);
});
for (let part=0; part<thisCellPart.length; part++) {
if (Boolean(thisCellPart[part]) == false) continue;
if (!options.onBeforeLineAdd(thisCellPart[part])) continue;
resultArray[part] = thisCellPart[part];
}
} else {
for (let col=0; col<row.length; col++) {
if (col == keyColumn) continue;
if (typeof options.priorityCol !=='undefined') {
if (col == options.priorityCol) continue;
}
let thisCell = row[col]||"";
let thisCellPart = thisCell.split("\n").map(function(input){
return common.stripCarriageReturn(input);
});
for (let part=0; part<thisCellPart.length; part++) {
if (Boolean(thisCellPart[part]) == false) continue;
if (!options.onBeforeLineAdd(thisCellPart[part])) continue;
resultArray[part] = thisCellPart[part];
}
}
}
if (options.includeIndex) {
let thisCell = row[keyColumn]||"";
let thisCellPart = thisCell.split("\n").map(function(input){
return common.stripCarriageReturn(input);
});
for (let part=0; part<thisCellPart.length; part++) {
if (Boolean(thisCellPart[part]) == false) continue;
if (!options.onBeforeLineAdd(thisCellPart[part])) continue;
resultArray[part] = thisCellPart[part];
}
}
if (typeof options.priorityCol !== 'undefined') {
var thisCell = row[options.priorityCol]||"";
var thisCellPart = thisCell.split("\n").map(function(input){
return common.stripCarriageReturn(input);
});
for (var part=0; part<thisCellPart.length; part++) {
if (Boolean(thisCellPart[part]) == false) continue;
//if (!options.onBeforeLineAdd(thisCellPart[part])) continue;
resultArray[part] = thisCellPart[part];
}
}
return resultArray.join(options.lineBreak);
}
/**
* Generates translation pair
* @param {String[][]} data - Two dimensional array represents the table
* @param {Number} translationCol - Column index of the preferred translation
* @returns {Object} - Key-value pair of translation
*/
Trans.prototype.generateTranslationPair = function(data, translationCol) {
translationCol = translationCol || 0;
if (Array.isArray(data) == false) return {};
var result = {};
for (var rowId=0; rowId<data.length; rowId++) {
if (Boolean(data[rowId][0]) == false) continue;
var translation = trans.getTranslationFromRow(data[rowId], translationCol);
if (translation == null) continue;
result[data[rowId][0]] = translation;
}
return result;
}
/**
* Count how many rows are translated
* @param {(String|String[])} file - The file ID(s) to process
* @returns {TranslationStats}
*/
Trans.prototype.countTranslated = function(file) {
file = file||[];
if (typeof file == 'string') {
file=[file];
}
if (file.length == 0) file = this.getAllFiles()
var result = {};
for (var i=0; i<file.length; i++) {
var thisData = this.project.files[file[i]].data;
/**
* @typedef {Object} TranslationStats
* @property {number} translated - How many rows are translated
* @property {number} length - The number rows in total
* @property {number} percent - How many rows are translated
*/
result[file[i]] = {
translated :0,
length :0,
percent :100
};
for (var row=0; row<thisData.length; row++) {
if (Boolean(thisData[row][this.keyColumn]) == false) continue;
thisData[row][this.keyColumn] = thisData[row][this.keyColumn]||"";
// stringify
thisData[row][this.keyColumn] = thisData[row][this.keyColumn]+""
if (this.rowByRowMode) {
// DO NOT USE, evaluating by rows
try {
var thisKeyCount = thisData[row][0].split("\n").length;
} catch (e) {
console.error("Error when trying to split key string ", thisData[row][0]);
throw(e)
}
for (var col=1; col<thisData[row].length; col++) {
if (Boolean(thisData[row][col]) == false) continue;
// converts non string cell into string
if (typeof(thisData[row][col]) !== 'string') thisData[row][col] = thisData[row][col]+'';
thisData[row][col] = thisData[row][col]||"";
var thisColCount = thisData[row][col].split("\n").length;
if (thisColCount>=thisKeyCount) {
result[file[i]].translated ++;
break;
}
}
} else {
if (this.rowHasTranslation(thisData[row], this.keyColumn)) result[file[i]].translated++;
}
result[file[i]].length++;
}
if (result[file[i]].length > 0) result[file[i]].percent = result[file[i]].translated/result[file[i]].length*100;
if (result[file[i]].percent > 100) result[file[i]].percent = 100;
if (result[file[i]].percent < 0) result[file[i]].percent = 0;
this.project.files[file[i]].progress = result[file[i]];
}
return result;
}
/**
* Resets the index of all files
*/
Trans.prototype.resetIndex = function(hardReset) {
hardReset = hardReset || false;
for (var id in this.project.files) {
this.project.files[id].indexIsBuilt = false;
}
}
/**
* building indexes from trans data for faster search KEY by ID
* This function will cache the result in default index's cache location which is: `trans.project.files[fileId].indexID`
* @param {String} fileId - The file ID to process
* @param {Boolean} [rebuild=false] - Force to rebuld index if index is already exist
* @param {Number} [keyColumn=0] - Key column of the table
* @returns {Object} Key-value pair of the index
*/
Trans.prototype.buildIndex = function(fileId, rebuild, keyColumn) {
fileId = fileId||trans.getSelectedId();
keyColumn = keyColumn || trans.keyColumn || 0;
if (fileId) {
var currentObject = trans.project.files[fileId];
if (currentObject.indexIsBuilt && rebuild !== true) return currentObject.indexIds;
var result = {};
for (let i=0; i<currentObject.data.length; i++) {
//console.log("registering : "+currentObject.data[i][0]);
if (typeof currentObject.data[i] == 'undefined') continue;
if (currentObject.data[i][keyColumn] == null || currentObject.data[i][keyColumn] == '' || typeof currentObject.data[i][keyColumn] == 'undefined') continue;
result[currentObject.data[i][keyColumn]] = i;
}
currentObject.indexIds = result;
currentObject.indexIsBuilt = true;
if (fileId == trans.getSelectedId()) {
trans.indexIds = currentObject.indexIds;
trans.indexIsBuilt = currentObject.indexIsBuilt;
}
return result;
} else {
if (trans.indexIsBuilt && rebuild !== true) return trans.indexIds;
for (let i=0; i<trans.data.length; i++) {
if (typeof trans.data[i] == 'undefined') continue;
if (trans.data[i][keyColumn] == null || trans.data[i][keyColumn] == '' || typeof trans.data[i][keyColumn] == 'undefined') continue;
trans.indexIds[trans.data[i][keyColumn]] = i;
}
trans.indexIsBuilt = true;
return trans.indexIds;
}
}
/**
* Build indexes from multiple files
* @param {String[]} files - The file ID to process
* @param {Boolean} lineByLine - Whether the index processed with line-by-line algorithm or no. Default row-by-row algorithm
* @param {Object} options
* @returns {Object} Key-Object of the value pair of the index
* @since 4.7.15
*/
Trans.prototype.buildIndexes = function(files, lineByLine=false, options={}) {
options ||= {};
options.customFilter;
options.indexId ||= "";
//todo : custom index
this.customIndexes ||= {};
if (typeof options.customFilter == "function") {
console.log("Generating custom index mode");
try {
if (typeof options.customFilter("test") !== "string") return console.error("Invalid customFilter. Custom filter should return string")
} catch (e) {
return console.error("Invalid customFilter. Custom filter should return string")
}
options.indexId = "#auto.fn."+common.crc32String(options.customFilter.toString());
}
if (options.indexId) {
this.customIndexes[options.indexId] = {}
}
if (typeof files == "string") files = [files];
files = files || [];
if (files.length == 0) { // that means ALL!
files = this.getAllFiles();
}
var result = {};
if (lineByLine) {
for (let i=0; i<files.length; i++) {
let thisObj = this.getObjectById(files[i]);
if (!thisObj) continue;
if (!thisObj.indexIsBuilt) this.buildIndex(files[i]);
let thisIndexIds = this.getObjectById(files[i]).indexIds;
for (var keyText in thisIndexIds) {
var keys = keyText.replaceAll("\r", "").split("\n");
for (var line=0; line<keys.length;line++) {
let key = keys[line];
if (typeof options.customFilter == "function") key = options.customFilter(key);
result[key] = result[key] || [];
result[key].push({
file :files[i],
row :thisIndexIds[keyText],
line :line
})
}
}
}
if (options.indexId) {
this.customIndexes[options.indexId] = result;
} else {
this._tempIndexes = result;
}
return result;
}
// row by row algorithm
console.log("Files is", files);
for (let i=0; i<files.length; i++) {
console.log("handling file:", files[i]);
let thisObj = this.getObjectById(files[i]);
console.log("ThisObject:", thisObj);
if (!thisObj) continue;
if (!thisObj.indexIsBuilt) this.buildIndex(files[i]);
let thisIndexIds = this.getObjectById(files[i]).indexIds;
for (let key in thisIndexIds) {
if (typeof options.customFilter == "function") key = options.customFilter(key);
result[key] = result[key] || [];
result[key].push({
file:files[i],
row:thisIndexIds[key]
})
}
}
if (options.indexId) {
this.customIndexes[options.indexId] = result;
} else {
this._tempIndexes = result;
}
console.log("Indexes is", result);
return result;
}
/**
* Get data from index
* @param {String} keyword - Keyword to search for
* @param {Object} [indexes] - Key value pair of indexes
* @param {Function} [customFilter] - Function custom filter used when building the index
* @returns {String|undefined}
*/
Trans.prototype.getFromIndexes = function(keyword, indexes, customFilter) {
if (typeof customFilter == "function") {
var target = "#auto.fn."+common.crc32String(customFilter.toString())
if (this.customIndexes[target]) {
this.customIndexes[target][keyword];
}
}
indexes = indexes || this._tempIndexes || {};
return indexes[keyword];
}
/**
* Clear temporary index
* @param {Function|String} [target] - Target custom index. If empty this function will remove all indexes.
*/
Trans.prototype.clearTemporaryIndexes = function(target) {
if (target) {
if (typeof target == "string") {
if (this.customIndexes[target]) delete this.customIndexes[target];
} else if (typeof target == "function") {
target = "#auto.fn."+common.crc32String(target.toString())
if (this.customIndexes[target]) delete this.customIndexes[target];
}
} else {
delete this.customIndexes
}
this._tempIndexes = undefined;
}
/**
* Find the row index by text
* @param {String} index - Index to look for
* @param {String} fileId - The file ID to process
* @returns {Number} The row index of the key string
*/
Trans.prototype.findIdByIndex = function(index, fileId) {
if (trans.data == null || trans.data == '' || typeof trans.data == 'undefined') return false;
if (typeof fileId == 'undefined') {
if (typeof trans.indexIds == 'undefined') trans.buildIndex();
if (typeof trans.indexIds[index] == "undefined") return false;
return trans.indexIds[index];
} else {
if (trans.project.files[fileId].indexIsBuilt == false) trans.buildIndex(fileId);
if (typeof trans.project.files[fileId].indexIds == 'undefined') trans.buildIndex(fileId);
//console.log(trans.project.files[fileId].indexIds);
if (typeof trans.project.files[fileId].indexIds[index] == 'undefined') return false;
return trans.project.files[fileId].indexIds[index];
}
}
Trans.prototype.getClearOnNextHumanInteract = function(key) {
this._clearOnNextHumanInteract = this._clearOnNextHumanInteract || {};
if (!key) return this._clearOnNextHumanInteract;
this._clearOnNextHumanInteract[key] = this._clearOnNextHumanInteract[key] || {};
return this._clearOnNextHumanInteract[key];
}
/**
* Get the row ID by text. Case insensitive.
* @param {String} str - text to look for
* @param {String} fileId - File to look for
* @returns {Number} Row index
*/
Trans.prototype.getRowIdByTextInsensitive = function(str, fileId) {
if (this.data == null || this.data == '' || typeof this.data == 'undefined') return false;
if (!fileId) throw "second argument fileId is required!"
var thisIndex = this.getClearOnNextHumanInteract(`insensitiveIndex_${fileId}`);
var data = this.getData(fileId)
var makeInsensitive = (txt) => {
if (!txt) return "";
return common.trimRightParagraph(txt).toLowerCase();
}
if (empty(thisIndex)) {
// building index
for (var rowId=0; rowId<data.length; rowId++) {
var thisKey = makeInsensitive(data[rowId][this.keyColumn]);
thisIndex[thisKey] = rowId;
}
}
return thisIndex[makeInsensitive(str)]
}
/**
* Copy imported translation into the current objects with the same id and same row index
* @param {trans.project|trans.project.files} obj
* @param {Number} [targetColumn=1] - Target column
* @param {Object} options
* @param {String[]} options.files - File IDs to process
* @param {'lineByLine'|'rowByRow'} [options.mode=lineByLine] - Translation mode
* @param {'translated'|'untranslated'|'both'} [options.fetch=translated] - Fetch translated only, or untranslated only, or both
* @param {String[]} [options.filterTag] - Tags filter
* @param {'blacklist'|'whitelist'} [options.filterTagMode] - Filter mode
* @param {Boolean} [options.overwrite=false] - Whether to overwrite or not if the destination cell is not empty
* @param {Number} options.targetColumn
*/
Trans.prototype.copyTranslationToRow = function(obj, targetColumn, options) {
console.log("copyTranslationToRow", arguments);
if (typeof obj == 'undefined') return false;
if (typeof obj.files !== 'undefined') obj = obj.files;
targetColumn = targetColumn || options.targetColumn || 1;
options = options||{};
options.files = options.files||[];
options.mode = options.mode||"";
options.fetch = options.fetch||"";
options.filterTag = options.filterTag || [];
options.filterTagMode = options.filterTagMode || "";
options.overwrite = options.overwrite || false;
if (Array.isArray(options.files) == false) options.files = [options.files];
if (options.files.length == 0) {
for (let file in obj) {
options.files.push(file);
}
}
for (var i=0; i<options.files.length; i++) {
let file = options.files[i];
console.log("Handling", file);
if (Boolean(obj[file]) == false) continue;
var objWithSameId = trans.getObjectById(file);
for (var row=0; row<obj[file].data.length; row++) {
var thisRow = obj[file].data[row]
if (Boolean(thisRow[0]) == false) continue;
if (empty(objWithSameId.data[row])) continue;
if (options.overwrite == false && Boolean(objWithSameId.data[row][targetColumn])) continue;
var thisTranslation = trans.getTranslationFromRow(thisRow);
if (Boolean(thisTranslation) == false) thisTranslation = thisRow[0];
objWithSameId.data[row][targetColumn] = thisTranslation;
}
}
}
/**
* Generates context translation pair
* @param {trans.project|trans.project.files} obj - trans.project or trans.project.files format
* @param {Object} options
* @param {String[]} options.files - File IDs to process
* @param {'lineByLine'|'rowByRow'} [options.mode=lineByLine] - Translation mode
* @param {'translated'|'untranslated'|'both'} [options.fetch=translated] - Fetch translated only, or untranslated only, or both
* @param {String[]} [options.filterTag] - Tags filter
* @param {'blacklist'|'whitelist'} [options.filterTagMode] - Filter mode
* @returns {Object} translation table object {key: "translation strings"}
*/
Trans.prototype.generateContextTranslationPair = function(obj, options) {
// return translation table object
// Normal mode :
// {key: "translation strings"}
//
// only on lineByLine mode :
// options.fetch
// translated, untranslated, both
// default: translated
console.log("Entering trans.generateTranslationTable");
if (typeof obj == 'undefined') return false;
if (typeof obj.files !== 'undefined') obj = obj.files;
console.log("obj files inside trans.generateTranslationTable :");
console.log(obj);
var result = {};
options = options||{};
options.files = options.files||[];
options.mode = options.mode||"";
options.fetch = options.fetch||"";
options.filterTag = options.filterTag || [];
options.filterTagMode = options.filterTagMode || "";
if (Array.isArray(options.files) == false) options.files = [options.files];
if (options.files.length == 0) {
for (let file in obj) {
options.files.push(file);
}
}
console.log("Worked option files : ");
console.log(options.files);
for (let i=0; i<options.files.length; i++) {
let file = options.files[i];
if (Boolean(obj[file]) == false) continue;
for (var row=0; row<obj[file].context.length; row++) {
if (Boolean(obj[file].context[row]) == false) continue;
if (Boolean(obj[file].data[row]) == false) continue;
var thisTranslation = trans.getTranslationFromRow(obj[file].data[row]);
if (Boolean(thisTranslation) == false) thisTranslation = obj[file].data[row][trans.keyColumn];
if (Boolean(thisTranslation) == false) continue;
for (var contextId=0; contextId<obj[file].context[row].length; contextId++) {
var thisContextKey = obj[file].context[row][contextId];
result[thisContextKey] = thisTranslation;
}
}
}
console.log("translation table collection is : ");
console.log(result);
return result;
}
/**
* Generates context translation table row by row mode
* @param {trans.project|trans.project.files} obj - trans.project or trans.project.files format
* @param {Object} options
* @param {String[]} options.files - File IDs to process
* @param {'lineByLine'|'rowByRow'} [options.mode=lineByLine] - Translation mode
* @param {'translated'|'untranslated'|'both'} [options.fetch=translated] - Fetch translated only, or untranslated only, or both
* @param {String[]} [options.filterTag] - Tags filter
* @param {'blacklist'|'whitelist'} [options.filterTagMode] - Filter mode
* @param {Boolean} [options.caseSensitive=false] - Whether case sensitive or not
* @returns {Object} translation table object {key: "translation strings"}
*/
Trans.prototype.generateTranslationTable = function(obj, options) {
// return translation table object
// Normal mode :
// {key: "translation strings"}
// Line by Line mode :
// {keyLine1: "translation line1"}
// options.mode = default||lineByLine
//
// only on lineByLine mode :
// options.fetch
// translated, untranslated, both
// default: translated
console.log("Entering trans.generateTranslationTable");
if (typeof obj == 'undefined') return false;
if (typeof obj.files !== 'undefined') obj = obj.files;
console.log("obj files inside trans.generateTranslationTable :");
console.log(obj);
var result = {};
options = options||{};
options.files = options.files||[];
options.caseSensitive = options.caseSensitive||false; // aware of extension
options.mode = options.mode||"";
options.fetch = options.fetch||"";
options.filterTag = options.filterTag || [];
options.filterTagMode = options.filterTagMode || "";
if (Array.isArray(options.files) == false) options.files = [options.files];
if (options.files.length == 0) {
for (let file in obj) {
options.files.push(file);
}
}
console.log("Worked option files : ");
console.log(options.files);
// LINE BY LINE MODE
if (options.mode.toLowerCase() == "linebyline") {
for (let i=0; i<options.files.length; i++) {
let file = options.files[i];
console.log(file);
for (var row=0; row<obj[file].data.length; row++) {
if (Boolean(obj[file].data[row][0]) == false) continue;
if (options.filterTagMode == "blacklist") {
if (this.hasTags(options.filterTag, row, file)) continue;
} else if (options.filterTagMode == "whitelist") {
if (!this.hasTags(options.filterTag, row, file)) continue;
}
let thisTranslation = trans.getTranslationFromRow(obj[file].data[row]);
if (options.fetch == "untranslated") {
if (Boolean(thisTranslation) == true) continue;
} else if (options.fetch == "both") {
// do nothing
} else {
if (Boolean(thisTranslation) == false) continue;
}
/*
var splitedIndex = obj[file].data[row][0].split("\n").
map(function(input) {
input = input||"";
return input.replace(/\r/g, "")
});;
*/
// I thin'k no need to strip \r from key element
var splitedIndex = obj[file].data[row][0].split("\n");
thisTranslation = thisTranslation||"";
var splitedResult = thisTranslation.split("\n").
map(function(input) {
input = input||"";
return input.replaceAll(/\r/g, "")
});
for (var x=0; x<splitedIndex.length; x++) {
if (options.fetch == "untranslated") {
if (Boolean(splitedResult[x]) == true) continue;
} else if (options.fetch == "both") {
// do nothing
} else {
if (Boolean(splitedResult[x]) == false) continue;
}
result[splitedIndex[x]] = splitedResult[x]||"";
}
}
}
console.log("translation table collection is : ");
console.log(result);
return result;
}
// DEFAULT MODE!
for (let i=0; i<options.files.length; i++) {
let file = options.files[i];
//console.log("Handling obj file : ", file, obj[file]);
if (Boolean(obj[file]) == false) continue;
for (let row=0; row<obj[file].data.length; row++) {
if (Boolean(obj[file].data[row][0]) == false) continue;
let thisTranslation = trans.getTranslationFromRow(obj[file].data[row]);
if (Boolean(thisTranslation) == false) continue;
result[obj[file].data[row][0]] = thisTranslation;
}
}
console.log("translation table collection is : ");
console.log(result);
return result;
}
/**
* Generate translation table line by line mode
* @param {trans.project|trans.project.files} obj
* @param {Number} [targetColumn=1] - Target column
* @param {Object} options
* @param {String[]} options.files - File IDs to process
* @param {'lineByLine'|'rowByRow'} [options.mode=lineByLine] - Translation mode
* @param {'translated'|'untranslated'|'both'} [options.fetch=translated] - Fetch translated only, or untranslated only, or both
* @param {String[]} [options.filterTag] - Tags filter
* @param {'blacklist'|'whitelist'} [options.filterTagMode] - Filter mode
* @param {Boolean} [options.overwrite=false] - Whether to overwrite or not if the destination cell is not empty
* @param {Boolean} [options.ignoreTranslated=false] - Whether to skip processing or not if the row already has translation
* @param {Number} options.targetColumn
* @returns {Object} translation table object {key: "translation strings"}
*/
Trans.prototype.generateTranslationTableLine = function(obj, options) {
// return translation table object
// Normal mode :
// {key: "translation strings"}
// Line by Line mode :
// {keyLine1: "translation line1"}
// options.mode = default||lineByLine
//
// only on lineByLine mode :
// options.fetch
// translated, untranslated, both
// default: translated
console.log("Entering trans.generateTranslationTableLine", obj, options);
if (typeof obj == 'undefined') return false;
if (typeof obj.files !== 'undefined') obj = obj.files;
console.log("obj files inside trans.generateTranslationTable :");
console.log(obj);
var result = {};
options = options||{};
options.files = options.files||[];
options.caseSensitive = options.caseSensitive||false; // aware of extension
options.mode = options.mode||"";
options.fetch = options.fetch||"";
options.keyColumn = options.keyColumn||0;
options.filterTag = options.filterTag || [];
options.filterTagMode = options.filterTagMode || "";
options.ignoreTranslated = options.ignoreTranslated || false;
options.overwrite = options.overwrite || false;
options.collectAddress = options.collectAddress || false;
options.targetColumn;
try {
options.filterLanguage = options.filterLanguage||this.getSl()||"ja"; // japanese
} catch(e) {
options.filterLanguage = "ja"; // japanese
}
options.ignoreLangCheck = options.ignoreLangCheck||false;
console.log("ignore language check?");
console.log(options.ignoreLangCheck);
if (Array.isArray(options.files) == false) options.files = [options.files];
if (options.files.length == 0) {
for (let file in obj) {
options.files.push(file);
}
}
console.log("Worked option files : ");
console.log(options.files);
const addresses = {}
for (let i=0; i<options.files.length; i++) {
let file = options.files[i];
console.log("fetching translatable data from:", file);
for (let row=0; row<obj[file].data.length; row++) {
if (Boolean(obj[file].data[row][options.keyColumn]) == false) continue;
if (options.filterTagMode == "blacklist") {
if (this.hasTags(options.filterTag, row, file)) continue;
} else if (options.filterTagMode == "whitelist") {
if (!this.hasTags(options.filterTag, row, file)) continue;
}
if (options.ignoreTranslated) {
if (this.rowHasTranslation(obj[file].data[row], options.keyColumn)) continue;
}
if (options.overwrite == false && options.targetColumn) {
if (obj[file].data[row][options.targetColumn]) continue;
}
console.log("Reached here!");
var thisTranslation = trans.getTranslationFromRow(obj[file].data[row], options.keyColumn, [0]);
console.log("current translation", thisTranslation);
// I think no need to strip \r from key element
var splitedIndex = obj[file].data[row][options.keyColumn].split("\n");
thisTranslation = thisTranslation||"";
var splitedResult = thisTranslation.split("\n").
map(function(input) {
input = input||"";
return input.replaceAll(/\r/g, "")
});
for (var x=0; x<splitedIndex.length; x++) {
// if (options.ignoreWhitespace) {
// if (!splitedResult[x]) continue;
// if (!splitedResult[x].trim()) continue;
// }
if (options.fetch == "untranslated") {
if (Boolean(splitedResult[x]) == true) continue;
} else if (options.fetch == "both") {
// do nothing
} else {
if (Boolean(splitedResult[x]) == false) continue;
}
if (options.ignoreLangCheck == false) {
// skip collecting data if language doesn't match options.filterLanguage
if (common.isInLanguage(splitedIndex[x], options.filterLanguage) == false) continue;
}
result[splitedIndex[x]] = splitedResult[x]||"";
if (options.collectAddress) {
addresses[splitedIndex[x]] ||= [];
addresses[splitedIndex[x]].push({
line:x,
file:file,
row:row,
rowObj:obj[file].data[row]
})
}
}
}
}
console.log("result is : ");
console.log(result);
console.log("addresses:", addresses);
if (options.collectAddress) {
return {
pairs: result,
addresses: addresses
}
}
return result;
}
/**
* generate translation pair from strings
* @param {String|String[]} input - input can be string or array of string
* @param {TranslatorEngine} transEngine - Translator engine
* @param {Object} options
* @param {String} [options.filterLanguage=this.getSl()]
* @param {Boolean} [options.ignoreLangCheck=boolean]
* @param {Boolean} [options.ignoreLangCheck=boolean]
* @returns {Object} - include{'source text':'translation'},
* exclude{'filtered text':''}
*/
Trans.prototype.generateTranslationTableFromStrings = function(input, transEngine, options) {
/*
input can be string or array of string
generate translation pair from strings
return : include{'source text':'translation'},
exclude{'filtered text':''}
*/
options = options||{};
try {
options.filterLanguage = options.filterLanguage||this.getSl()||"ja"; // japanese
} catch(e) {
options.filterLanguage = "ja"; // japanese
}
options.ignoreLangCheck = options.ignoreLangCheck||false;
var ignoreLangCheck
try {
ignoreLangCheck = options.ignoreLangCheck || transEngine.getOptions("ignoreLangCheck");
} catch (e) {
ignoreLangCheck = false;
}
if (typeof input == 'string') input = [input];
var result = {
include:{},
exclude:{}
};
if (options.mode == "rowByRow") {
for (let i=0; i<input.length; i++) {
if (typeof input[i] != 'string') continue;
if (input[i].length < 1) continue;
if (!ignoreLangCheck) {
// skip collecting data if language doesn't match options.filterLanguage
if (common.isInLanguage(input[i], options.filterLanguage) == false) {
result.exclude[input[i]] = input[i];
continue;
}
}
result.include[input[i]] = "";
}
} else {
for (let i=0; i<input.length; i++) {
if (typeof input[i] != 'string') continue;
if (input[i].length < 1) continue;
var splitedIndex = input[i].replaceAll("\r", "").split("\n");
for (var x=0; x<splitedIndex.length; x++) {
if (options.ignoreWhitespace) {
if (!splitedIndex[x]) continue;
if (!splitedIndex[x].trim()) continue;
}
if (ignoreLangCheck) {
// skip collecting data if language doesn't match options.filterLanguage
if (common.isInLanguage(splitedIndex[x], options.filterLanguage) == false) {
result.exclude[splitedIndex[x]] = splitedIndex[x];
continue;
}
}
result.include[splitedIndex[x]] = "";
}
}
}
return result;
}
/**
* generate translation table from translation result
* @param {String[]} keywordPool - ["keyword1", "keyword2", ... ]
* @param {String[]} translationPool - ["translationOfKeyword1", "translationOfKeyword2", ...]
* @param {Object} defaultTrans - Default object
* @returns {Object} {"keyword1":"translationOfKeyword1", "keyword2":"translationOfKeyword2", ...}
*/
Trans.prototype.generateTranslationTableFromResult = function(keywordPool, translationPool, defaultTrans) {
/*
generate translation table from translation result
keywordPool = ["keyword1", "keyword2", ... ]
translationPool = ["translationOfKeyword1", "translationOfKeyword2", ...]
result :
translationTable
{"keyword1":"translationOfKeyword1", "keyword2":"translationOfKeyword2", ...};
*/
//console.log("generateTranslationTableFromResult Default translation:", arguments);
var result = defaultTrans || {};
for (var i=0; i<keywordPool.length; i++) {
if (typeof translationPool[i] == 'undefined') continue;
result[keywordPool[i]] = translationPool[i];
}
//console.log(JSON.stringify(result, undefined, 2))
return result;
}
/**
* Generates a translation table for the selected cells based on the provided parameters.
* @param {Array} [currentSelection] - The current selection of cells. If not provided, it defaults to the selected range in the grid.
* @param {string} [fileId] - The file ID. If not provided, it defaults to the currently selected file ID.
* @param {Object} [options] - Additional options for generating the translation table.
* @returns {Object} - The generated translation table.
*/
Trans.prototype.generateSelectedTranslationTable = function(currentSelection, fileId, options) {
currentSelection = currentSelection||this.grid.getSelectedRange()||[[]];
fileId = fileId || this.getSelectedId();
options = options || {};
var selectedCells = common.gridSelectedCells(currentSelection);
var thisData = this.getData(fileId)
var transTable = {};
for (var i=0; i<selectedCells.length; i++) {
var row = selectedCells[i].row
var col = selectedCells[i].col
var origText = thisData[row][this.keyColumn];
transTable[origText] = transTable[origText] || [];
transTable[origText].push({
col:col,
row:row,
value:thisData[row][col],
file:fileId
})
}
return transTable;
}
/**
* Word wrap a text
* @param {String} str
* @param {String[]} [rowTags=[]] - List of tags of the current row
* @param {String[]} [wordWrapByTags=[]] - List of tags to filter
* @param {String} [lineBreak="\n"] - Line break character
* @returns {String} Word wrapped text
*/
Trans.prototype.wordWrapText = function(str, rowTags=[], wordWrapByTags=[], lineBreak="\n") {
//console.log("wordWrap", arguments);
if (empty(wordWrapByTags)) return str;
if (empty(rowTags)) return str;
if (window.langTools?.isCJK(this.getTl())) {
for (let i=0; i<wordWrapByTags.length; i++) {
let intersect = common.arrayIntersect(rowTags, wordWrapByTags[i].tags);
if (intersect.length>0) return common.wordwrapLocale(str, wordWrapByTags[i].maxLength, this.getTl(), lineBreak);
}
}
for (let i=0; i<wordWrapByTags.length; i++) {
let intersect = common.arrayIntersect(rowTags, wordWrapByTags[i].tags);
if (intersect.length>0) return common.wordwrap(str, wordWrapByTags[i].maxLength, lineBreak);
}
return str;
}
/**
* Translation Data object generated by trans.getTranslationData
* @typedef {Object} TranslationData
* @property {Object} TranslationData.info - Information header of the translation pairs
* @property {Object} TranslationData.translationData - Translation data
* @property {Object} TranslationData.translationData[file] - list of translation pairs grouped by the file id
* @property {Object} TranslationData.translationData[file].info - Information of the current file
* @property {Object} TranslationData.translationData[file].translationPair - Key value pair of original text and translation
* @example
* {
"info": {
"filterTag": [],
"filterTagMode": ""
},
"translationData": {
"data/Actors.json": {
"info": {},
"translationPair": {
"key text": "translation",
"key text2": "translation2",
}
},
"data/Animations.json": {
"info": {},
"translationPair": {
"key text": "translation",
"key text2": "translation2",
}
}
}
}
*/
/**
* Generate Translation Data object
* @param {Trans} [transData=trans.getSaveData()] - instance of Trans object
* @param {Object} [options]
* @param {Object} [options.keyCol=0] - Key column of the table
* @param {Object} [options.groupIndex] - index added for the translation pair prefixes
* @param {String} [options.groupBy=path] - Group by what key. Default is path. use "id" to group by the key instead.
* @param {Object} [options.options]
* @param {String[]} [options.filterTag] - Tags filter
* @param {'blacklist'|'whitelist'} [options.filterTagMode] - Filter mode
* @param {Object} [options.wordWrapByTags]
* @returns {TranslationData} Translation information
* @fires onGenerateTranslationData
*/
Trans.prototype.getTranslationData = function(transData, options) {
/*
Generate Translation Pair Advanced
Generate translation pair from project file
It will use relPath for group key
result :
{
info : {
groupLevel : 0 // integer
},
translationData: {
[groupName] : {
info: {
},
translationPair : {
"key" : "translation"
"group[separator]key" : "translation"
}
}
}
}
*/
options = options || {}
options.keyCol = options.keyCol|| 0;
options.groupIndex = options.groupIndex||undefined; // index added for the translation pair prefixes
options.groupBy = options.groupBy || "path";
options.objectGrouping = options.objectGrouping || false // if true child translation pair will be under a sub-object inside translationPair object
options.includeTagsInfo = options.includeTagsInfo || false;
transData = transData||trans.getSaveData()
transData = JSON.parse(JSON.stringify(transData));
transData.project = transData.project||{}
transData.project.files = transData.project.files||{};
// fix for filtertag mode inside options.options
// this happens in export mode
options.options = options.options || {};
options.filterTag = options.filterTag||options.options.filterTag||[];
options.filterTagMode = options.filterTagMode||options.options.filterTagMode||""; // whitelist or blacklist
options.wordWrapByTags = options.wordWrapByTags || options.options.wordWrapByTags || transData.project.options.wordWrapByTags;
options.useSelectedFiles ??= true;
options.disableEvent ||= false;
var contextSeparator = options.contextSeparator || "\n"
var autofillFiles = [];
if (options.useSelectedFiles) {
var checkbox = $(".fileList .data-selector .fileCheckbox:checked");
for (var i=0; i<checkbox.length; i++) {
autofillFiles.push(checkbox.eq(i).attr("value"));
}
}
options.files = options.files||autofillFiles||[];
if (options.groupBy == "id") {
// generating id based on key index
for (let fileId in transData.project.files) {
if (!transData.project.files[fileId]) continue;
transData.project.files[fileId].id = fileId;
}
}
var transGroup = {};
var info = {
filterTag : options.filterTag,
filterTagMode : options.filterTagMode
};
if (!empty(options.wordWrapByTags)) {
info.wordWrapByTags = options.wordWrapByTags
}
for (let fileId in transData.project.files) {
var thisFiles = transData.project.files[fileId];
thisFiles.data = thisFiles.data||[[]];
thisFiles.tags = thisFiles.tags||[];
if (options.files.length > 0) {
if (options.files.includes(fileId) == false) continue;
}
var thisData = {
info:{
groupLevel : thisFiles['groupLevel']
},
translationPair : {}
}
transGroup[thisFiles[options.groupBy]] = transGroup[thisFiles[options.groupBy]] || thisData;
for (var row=0; row<thisFiles.data.length; row++) {
if (Boolean(thisFiles.data[row]) == false) continue;
if (Boolean(thisFiles.data[row][options.keyCol]) == false) continue;
var thisTag = thisFiles.tags[row] || [];
if (options.filterTagMode !== "") {
var intersect = options.filterTag.filter(value => thisTag.includes(value));
if (options.filterTagMode == "whitelist") {
if (intersect.length == 0) continue;
} else { // other than whitelist always assume blacklist
if (intersect.length > 0) continue;
}
}
try {
var originalWord = thisFiles.data[row][options.keyCol] = thisFiles.data[row][options.keyCol] || "";
var thisTranslation = trans.getTranslationFromRow(thisFiles.data[row], options.keyCol);
var transByContext = ui.translationByContext.generateContextTranslation(row, fileId, originalWord);
if ((Boolean(thisTranslation) == false) && transByContext.length == 0) continue
//console.log("Group result by a key in trans.project.files");
if (transByContext.length > 0) transGroup[thisFiles[options.groupBy]].translationPair = Object.assign(transGroup[thisFiles[options.groupBy]].translationPair, transByContext.translation)
if (options.objectGrouping) {
if (thisFiles[options.groupIndex]) {
//assign to sub object
//console.log("assigning to child object",thisFiles[options.groupIndex]);
transGroup[thisFiles[options.groupBy]].translationPair[thisFiles[options.groupIndex]] = transGroup[thisFiles[options.groupBy]].translationPair[thisFiles[options.groupIndex]] || {};
if ((Boolean(thisTranslation) !== false)) transGroup[thisFiles[options.groupBy]].translationPair[thisFiles[options.groupIndex]][originalWord] = trans.wordWrapText(thisTranslation, thisTag, options.wordWrapByTags);
} else {
//direct translation string
if ((Boolean(thisTranslation) !== false)) transGroup[thisFiles[options.groupBy]].translationPair[originalWord] = trans.wordWrapText(thisTranslation, thisTag, options.wordWrapByTags);
}
} else {
var thisKey = thisFiles[options.groupIndex] ? thisFiles[options.groupIndex]+contextSeparator+originalWord : originalWord
if ((Boolean(thisTranslation) !== false)) transGroup[thisFiles[options.groupBy]].translationPair[thisKey] = trans.wordWrapText(thisTranslation, thisTag, options.wordWrapByTags);
}
} catch (e) {
console.log("Error when processing", fileId, "row", row, thisFiles.data[row][options.keyCol]);
throw(e);
}
//console.log("at the end : ", transGroup[thisFiles[options.groupBy]]);
}
}
/**
* @event Trans#onGenerateTranslationData
* @param {Object} options
* @param {Object} options.info
* @param {Object} options.translationData
*/
if (!options.disableEvent) {
this.trigger("onGenerateTranslationData", {
info:info,
translationData:transGroup
});
}
return {
info:info,
translationData:transGroup
}
}
/**
* Build context from parameter
* @param {Object} parameter - Object paramter
* @returns {string} context string
*/
Trans.prototype.buildContextFromParameter = function(parameter) {
return parameter['VALUE ID']+"/"+consts.eventCode[trans.gameEngine][parameter['EVENT CODE']];
}
// =====================================================================
// DATA VALIDATION & MANIPUlATION & PROJECT CREATION DATA INITIALIZATION
// =====================================================================
/**
* Get the staging path of the current project
* @param {Trans} [transData] - Trans Object to identify the staging path
* @returns {String|undefined} A full path to the stagging directory. Return undefined when fail
* @since 4.4.4
*/
Trans.prototype.getStagingPath = function(transData) {
try {
transData ||= this;
return nwPath.resolve(transData.project.cache.cachePath)
} catch (e) {
return;
}
}
/**
* Retrieves the staging data path based on the provided Trans data.
* @param {any} transData - The Trans data.
* @returns {string} - The staging data path.
*/
Trans.prototype.getStagingDataPath = function(transData) {
var defaultBaseData = engines.current().getProperty("stagingDataPath") || "data";
return nwPath.join(this.getStagingPath(transData), defaultBaseData);
}
/**
* Update gameInfo.json at staging location
* @param {Trans|undefined} [transData] - Trans data
*/
Trans.prototype.updateStagingInfo = async function(transData) {
transData ||= this;
const stagingInfoFile = nwPath.join(this.getStagingPath(transData)||"", "gameInfo.json");
console.log("Staging info:", stagingInfoFile);
var stagingInfo = {}
if (await common.isFileAsync(stagingInfoFile)) {
stagingInfo = JSON.parse(await common.fileGetContents(stagingInfoFile));
}
stagingInfo.title = transData.project.gameTitle;
stagingInfo.engine = transData.project.gameEngine;
await common.filePutContents(stagingInfoFile, JSON.stringify(stagingInfo, undefined, 2), "UTF8", false);
return stagingInfo;
}
/**
* Get the real path to the staging file
* @param {String|Object} obj - String file ID or object
* @since 4.6.29
* @returns {String} Path to the file
*/
Trans.prototype.getStagingFile = function(obj) {
if (typeof obj == "string") {
obj = this.getObjectById(obj)
}
if (!obj) return;
if (typeof obj !== "object") return;
if (!obj.path) return;
var stagintPath = nwPath.join(this.getStagingDataPath(), obj.path);
return stagintPath;
}
/**
* Insert cell
* @param {Number} index
* @param {String|null} value
*/
Trans.prototype.insertCell = function(index, value) {
value = value||null;
common.batchArrayInsert(trans.data, index, value);
if(typeof trans.project == "undefined") return trans.alert(t("Please open or create a new project first!"));
for (var file in trans.project.files) {
if (file == trans.getSelectedId()) continue;
common.batchArrayInsert(trans.project.files[file].data, index, value);
}
}
/**
* Copy column
* @param {Number} from
* @param {Number} to
* @param {Object} [project=trans.project]
* @param {Object} [options]
*/
Trans.prototype.copyCol = function(from, to, project, options) {
console.log("Copying column");
console.log(arguments);
options = options || {};
project = project || trans.project;
if (typeof project == 'undefined') return console.log("project is undefined");
if (typeof project.files == 'undefined') return console.log("project.files are undefined");
for (var file in project.files) {
if (Array.isArray(project.files[file].data) == false) {
console.log("no data for files "+file);
continue;
}
for (var row in project.files[file].data) {
project.files[file].data[row][to] = project.files[file].data[row][from];
}
}
}
/**
* Check whether the grid is modified or not
* @param {Boolean} [flag] - If flag is defined, then set the flag
* @returns {Boolean}
*/
Trans.prototype.gridIsModified =function(flag) {
if (typeof flag == 'undefined') return this.unsavedChange;
if (!this.project) {
this.unsavedChange = false;
return;
}
if (this.unsavedChange !== flag) {
/**
* Triggered when grid is modified
* @event Trans#documentModifiedStateChange
* @param {Boolean} flag
*/
this.trigger("documentModifiedStateChange", flag);
}
this.unsavedChange = flag;
//console.trace("Grid is modified");
var thisId = this.getSelectedId();
if (Boolean(this.project.files) == false) return false;
if (!this.project.files[thisId]) return false;
this.project.files[thisId]["cacheResetOnChange"] = {};
return this.unsavedChange;
}
/**
* iteratively select all files and return to the last selection
*/
Trans.prototype.walkToAllFile = function() {
console.log($(".fileList .selected"));
var current = $(".fileList li").index($(".fileList .selected"));
for (var i=0; i<$(".fileList li").length; i++) {
$(".fileList li").eq(i).trigger("click");
}
$(".fileList li").eq(current).trigger("click");
}
/**
* Move a column to the new index
* @param {Number} fromIndex - Source column index
* @param {Number} toIndex - Destination column index
* @returns {Trans} Instance of trans
*/
Trans.prototype.moveColumn = async function(fromIndex, toIndex) {
console.log("trans.moveColumn");
console.log(arguments);
if (typeof trans.project == 'undefined') return false;
if (toIndex > Math.min.apply(null, fromIndex)) {
console.log("move to rigth");
toIndex = toIndex-1;
}
if (fromIndex[0] == toIndex) return false;
if (toIndex < Math.max.apply(null, fromIndex)) {
console.log("move to left");
}
ui.showBusyOverlay();
for (var file in trans.project.files) {
//if (file == trans.getSelectedId()) continue;
for (var row=0; row<trans.project.files[file].data.length; row++) {
trans.project.files[file].data[row] = common.arrayMoveBatch(trans.project.files[file].data[row], fromIndex, toIndex);
// adjusting cellInfo
this.cellInfo.moveCell(file, row, fromIndex, toIndex);
}
}
//sorting colHeaders
trans.colHeaders = common.arrayMoveBatch(trans.colHeaders, fromIndex, toIndex);
//sorting column
trans.column = common.arrayMoveBatch(trans.column, fromIndex, toIndex);
await common.wait(250)
trans.grid.destroy();
trans.initTable();
ui.hideBusyOverlay();
trans.renderGridInfo();
trans.gridIsModified(true);
return trans;
}
/**
* Padding data by the length of header column
* @returns {Trans}
*/
Trans.prototype.dataPadding = function() {
// padding data by the length of header column
if (typeof trans.data == 'undefined') return false;
console.log("Trans data : ", trans.data);
for (let i in trans.data) {
if (Array.isArray(trans.data[i]) == false) trans.data[i] = [];
if (trans.data[i].length >= trans.colHeaders.length) continue;
let dif = trans.colHeaders.length - trans.data[i].length;
if (dif < 0) continue;
console.log("trans.data[i] : ", trans.data[i]);
console.log("trans.data : ", trans.data[i].length);
console.log("dif length : ", dif);
let padding = Array(dif).fill(null);
trans.data[i] = trans.data[i].concat(padding);
}
if (typeof trans.project == 'undefined') return false;
for (let file in trans.project.files) {
for (let i in trans.project.files[file].data) {
if (trans.project.files[file].data[i].length >= trans.colHeaders.length) continue;
let dif = trans.colHeaders.length - trans.project.files[file].data[i].length;
let padding = Array(dif).fill(null);
trans.project.files[file].data[i] = trans.project.files[file].data[i].concat(padding);
}
}
trans.refreshGrid();
return trans;
}
/**
* Generating header based on the maximum length of the data
* @param {Trans} [trans=this] - Trans data
* @param {String} [prefix]
* @returns {Object} Column header
*/
Trans.prototype.generateHeader = function(trans, prefix) {
// generating header based on the maximum length of the data
trans = trans || this;
prefix = prefix || "";
var maxLength = 0;
for (let file in trans.project.files) {
let currentData = trans.project.files[file].data;
currentData = currentData||[];
for (let i=0; i<currentData.length; i++) {
if (Array.isArray(currentData[i]) == false) continue;
if (currentData[i].length > maxLength) maxLength = currentData[i].length;
}
}
for (let i=trans.colHeaders.length-1; i<maxLength; i++) {
trans.colHeaders.push(prefix+String.fromCharCode(65+i))
trans.columns.push({});
}
return trans.colHeaders;
}
/**
* Sanitize instance of the Trans data
* @param {Trans} trans
* @returns {Trans}
*/
Trans.prototype.sanitize = function(trans) {
trans = trans || this;
console.log("running trans.sanitize");
if (typeof trans.project == 'undefined') return false;
if (typeof trans.project.files == 'undefined') return false;
//var rowPad = JSON.parse(JSON.stringify(this.colHeaders))
//rowPad.fill(null)
//console.log("data of rowPad : ", rowPad);
for (var file in trans.project.files) {
var currentData = trans.project.files[file].data;
var currentContext = trans.project.files[file].context||[];
var currentTags = trans.project.files[file].tags||[];
var currentParameters = trans.project.files[file].parameters||[];
var inCache = {};
var newData = [];
var newContext = [];
var newTags = [];
var newParameters = [];
this.colHeaders = this.colHeaders||[];
if (Array.isArray(currentData) == false) {
// if not an array, overwrite with blank array.
trans.project.files[file].data = [JSON.parse(JSON.stringify(this.colHeaders)).fill(null)];
continue;
} else if (currentData.length == 0) {
trans.project.files[file].data = [JSON.parse(JSON.stringify(this.colHeaders)).fill(null)];
continue;
}
currentData = currentData||[];
for (var i=0; i<currentData.length; i++) {
if (typeof currentData[i][0] !== 'string') continue;
if (currentData[i][0].length < 1) continue;
if (typeof inCache[currentData[i][0]] == 'undefined') {
newData.push(currentData[i]);
var row = newData.length - 1;
if (currentContext[i]) newContext[row] = currentContext[i];
if (currentTags[i]) newTags[row] = currentTags[i];
if (currentParameters[i]) newParameters[row] = currentParameters[i];
inCache[currentData[i][0]] = true;
}
}
trans.project.files[file].data = newData;
trans.project.files[file].context = newContext;
trans.project.files[file].tags = newTags;
trans.project.files[file].parameters = newParameters;
}
trans.project.isDuplicatesRemoved = true;
return trans;
}
/**
* Remove duplicate entries from current trans.data
* Should be run once on initialization
* @returns {string[][]} trans.data, two dimensional array of the grid
*/
Trans.prototype.removeDuplicates = function() {
console.log("running trans.removeDuplicates");
var inCache = {};
var newData = [];
for (var i=0; i<trans.data.length; i++) {
if (typeof inCache[trans.data[i][0]] == 'undefined') {
newData.push(trans.data[i]);
inCache[trans.data[i]] = true;
}
}
trans.data = newData;
return trans.data;
}
/**
* Check whether the key text is exist on a file
* @param {String} key - key text to find
* @param {String} fileId - File id to search for
* @returns {Boolean}
*/
Trans.prototype.isKeyExistOn = function(key, fileId) {
if (typeof fileId == 'undefined') fileId = trans.getSelectedId();
if (typeof trans.findIdByIndex(key, fileId) == 'number') {
return true;
} else {
return false;
}
}
/**
* Check whether the key text is exist on current active file
* @param {String} key - key text to find
* @returns {Boolean}
*/
Trans.prototype.isKeyExist = function(key) {
if (typeof trans.findIdByIndex(key) == 'number') {
return true;
} else {
return false;
}
}
// ============================================================
// TAGGING
// ============================================================
/**
* Set tags to the preferred row
* @param {String} file - File Id
* @param {Number} row - Row index
* @param {String|String[]} tags - tags to set
* @param {Object} options
* @param {Boolean} [options.append] - Whether to append or to override the tags with the current value
* @returns {String[]} The current tags of the preferred row
*/
Trans.prototype.setTags = function(file, row, tags, options) {
/* options : {
append : boolean
}
*/
options = options||{};
if (Array.isArray(tags) == false) tags = [tags];
if (typeof this.project == 'undefined') return false;
if (typeof this.project.files[file] == 'undefined') return false;
if (typeof row != 'number') return false;
if (typeof this.project.files[file].tags == 'undefined') this.project.files[file].tags = [];
if (Boolean(options.append) == true) {
this.project.files[file].tags[row] = this.project.files[file].tags[row]||[];
this.project.files[file].tags[row].push.apply(this.project.files[file].tags[row], tags);
this.project.files[file].tags[row] = this.project.files[file].tags[row].filter((v, i, a) => a.indexOf(v) === i);
} else {
this.project.files[file].tags[row] = tags;
}
//this.grid.render();
return this.project.files[file].tags[row];
}
/**
* Removes one or more tags from the preferred row
* @param {String} file - File Id
* @param {Number} row - Row index
* @param {String|String[]} tags - tags to set
* @param {Object} options
* @returns {String[]} The current tags of the preferred row
*/
Trans.prototype.removeTags = function(file, row, tags, options) {
options = options||{};
file = file||this.getSelectedId();
if (Array.isArray(tags) == false) tags = [tags];
if (typeof this.project == 'undefined') return false;
if (typeof this.project.files[file] == 'undefined') return false;
if (typeof row != 'number') return false;
if (typeof this.project.files[file].tags == 'undefined') this.project.files[file].tags = [];
let arr = this.project.files[file].tags[row];
arr = arr.filter(item => !tags.includes(item))
this.project.files[file].tags[row] = arr;
return this.project.files[file].tags[row];
}
/**
* Removes one or more tags from the preferred row
* @param {String} file - File Id
* @param {Number|CellRanges} cellRange - Row index
* @param {Object} options
* @returns {String[]} The current tags of the preferred row
*/
Trans.prototype.clearTags = function(file, cellRange, options) {
options = options||{};
file = file||this.getSelectedId();
if (typeof this.project == 'undefined') return false;
if (typeof this.project.files[file] == 'undefined') return false;
if (typeof cellRange == 'number') {
this.project.files[file].tags[cellRange] = [];
return this.project.files[file].tags[cellRange];
}
for (let i=0; i<cellRange.length; i++) {
var cellStart = cellRange[i].start || cellRange[i].from;
var cellEnd = cellRange[i].end || cellRange[i].to;
if (typeof cellStart.row == 'undefined') continue
if (typeof cellEnd.row == 'undefined') continue
this.project.files[file].tags = this.project.files[file].tags||[];
for (var row=cellStart.row; row <= cellEnd.row; row++) {
this.project.files[file].tags[row] = [];
}
}
return this.project.files[file].tags[row];
}
/**
* Removes all tags settings from a file
* @param {String} file - File Id
* @param {Object} options
* @returns {String[]} The current tags of the preferred file, which is an empty array
*/
Trans.prototype.resetTags = function(file, options) {
options = options||{};
file = file||this.getSelectedId();
if (typeof this.project == 'undefined') return false;
if (typeof this.project.files[file] == 'undefined') return false;
return this.project.files[file].tags = [];
}
/**
* Append tags into some rows
* @param {String} file - File Id
* @param {Number} row - Row index
* @param {String|String[]} tags - tags to set
* @returns {String[]} The current tags of the preferred row
*/
Trans.prototype.appendTags = function(file, row, tags, options) {
return this.setTags(file, row, tags, {append:true, noRefresh:true})
}
/**
* HOT's CellRange object
* @typedef {Object} CellRange
* @property {Object} CellRange.from - The starting cell
* @property {Object} CellRange.to - The latest selected cell
* @property {Object} CellRange.highlight - The highlighted cell
* @example
* {
"highlight": {
"row": 8,
"col": 2
},
"from": {
"row": 8,
"col": 2
},
"to": {
"row": 10,
"col": 2
}
}
*/
/**
* Set tag for each CellRange object
* @param {String|String[]} tagName - Tags to put into
* @param {CellRange[]} [cellRange=trans.grid.getSelectedRange()] - Cell Range object
* @param {String} file - File Id
* @param {Object} options
* @param {Boolean} options.append - Whether to append or to override the current value with the new value
* @returns {Boolean} True on success
*/
Trans.prototype.setTagForSelectedRow = function(tagName, cellRange, file, options) {
/* from cellRange object or from simple coords
cellRange :[{"start":{"row":4,"col":2},"end":{"row":4,"col":2}}]
or
result from trans.grid.getSelectedRange()
*/
options = options||{};
if (typeof options.append == 'undefined' ) options.append = true;
file = file||this.getSelectedId();
if (Boolean(cellRange) == false) return false;
if (Array.isArray(cellRange) == false) return false;
for (let i=0; i<cellRange.length; i++) {
var cellStart = cellRange[i].start || cellRange[i].from;
var cellEnd = cellRange[i].end || cellRange[i].to;
if (typeof cellStart.row == 'undefined') continue
if (typeof cellEnd.row == 'undefined') continue
for (var row=cellStart.row; row <= cellEnd.row; row++) {
this.setTags(file, row, tagName, options);
}
}
this.grid.render();
return true;
}
/**
* Remove tags with cellRange object
* @param {String|String[]} tagName - Tags to put into
* @param {CellRange[]} [cellRange=trans.grid.getSelectedRange()] - Cell Range object
* @param {String} file - File Id
* @param {Object} options
* @returns {Boolean} True on success
*/
Trans.prototype.removeTagForSelectedRow = function(tagName, cellRange, file, options) {
/* from cellRange object or from simple coords
cellRange :[{"start":{"row":4,"col":2},"end":{"row":4,"col":2}}]
or
result from trans.grid.getSelectedRange()
*/
options = options||{};
options.append = options.append||true;
file = file||this.getSelectedId();
if (Boolean(cellRange) == false) return false;
if (Array.isArray(cellRange) == false) return false;
for (let i=0; i<cellRange.length; i++) {
var cellStart = cellRange[i].start || cellRange[i].from;
var cellEnd = cellRange[i].end || cellRange[i].to;
if (typeof cellStart.row == 'undefined') continue
if (typeof cellEnd.row == 'undefined') continue
for (var row=cellStart.row; row <= cellEnd.row; row++) {
this.removeTags(file, row, tagName, options);
}
}
this.grid.render();
return true;
}
/**
* Check whether a row has tags or not
* @param {String|String[]} tags - Check whether a row has one of these tags
* @param {Number|Number[]} row - Row(s) to check for
* @param {String} file - the file Id
* @returns {Boolean} true on success
*/
Trans.prototype.hasTags = function(tags, row, file) {
if (!tags) return false;
if (typeof row == 'undefined') return false;
file = file || this.getSelectedId();
var fileObj = this.getObjectById(file);
if (!fileObj) return false;
if (!fileObj.tags) return false;
if (!Array.isArray(fileObj.tags[row])) return false;
if (!Array.isArray(tags)) tags = [tags]
try {
for (var i in tags) {
if (fileObj.tags[row].includes(tags[i])) return true;
}
return false;
} catch (e) {
return false;
}
}
/**
* Display alert
* @async
* @param {String} text - Text to display
* @param {Number} [timeout=3000] - Timeout in miliseconds
*/
Trans.prototype.alert = async function(text, timeout) {
timeout = timeout||3000;
$("#appInfo").attr("title", text);
return new Promise((resolve, reject) => {
$("#appInfo").tooltip({
content:function() {
return text;
},
show: {
effect: "slideDown",
duration: 200
},
hide: {
effect: "fade",
delay: 250
},
position: {
my: "left top",
at: "left bottom",
of: "#table"
},
open: function( event, ui ) {
setTimeout(function(){
$("#appInfo").tooltip("close");
$("#appInfo").attr("title", "");
resolve();
}, timeout);
}
});
$("#appInfo").tooltip("open");
})
}
/**
* Refresh the grid
* @param {Object} options
* @param {Boolean} options.rebuild - Whether or not to rebuild the grid
* @param {Function} options.onDone - Function to run when the process is done
*/
Trans.prototype.refreshGrid = function(options) {
options = options||{};
options.rebuild = options.rebuild||false;
if(trans.getSelectedId()) {
trans.data = trans?.project?.files[trans.getSelectedId()].data;
}
if (!(trans.data)) trans.data = [[null]];
if (trans.data.length == 1) {
if (Array.isArray(trans.data[0]) == false) trans.data = [[null]]
if (Boolean(trans.data[0][0]) == false) trans.data = [[null]]
}
if (trans.data.length == 0) trans.data = [[null]]
if(trans.getSelectedId()) {
// re assign data in to trans.project.files[trans.getSelectedId()].data
// in case data is detached;
trans.project.files[trans.getSelectedId()].data = trans.data;
}
if (typeof options.onDone == 'function') {
trans.grid.addHookOnce('afterRender', function() {
options.onDone.call(trans.grid);
});
}
if (options.rebuild) {
trans.grid.destroy();
trans.initTable();
return true;
}
trans.grid.updateSettings({
data : trans.data,
colHeaders : trans.colHeaders,
columns : trans.columns
});
// TODO: for performance, should cache this:
// https://handsontable.com/docs/comments/#basic-example
trans.loadComments();
}
/**
* Open note at cell
* @param {Object} cell
* @param {Number} cell.row - Row ID
* @param {Number} cell.col - Column ID
*/
Trans.prototype.editNoteAtCell = function(cell) {
/*
cel : {row : 0, col: 0}
*/
if (typeof cell == 'undefined') {
try{
cell = trans.grid.getSelectedRange()[0]['highlight'];
} catch (error) {
console.log(error);
return false;
}
}
var hotComment = trans.grid.getPlugin('comments');
hotComment.showAtCell(cell.row, cell.col);
$(".htCommentTextArea").trigger("click");
$(".htCommentTextArea").focus();
}
/**
* Remove note at selected cell
* @param {CellRange[]} selection - Selected cell(s)
*/
Trans.prototype.removeNoteAtSelected = function(selection) {
console.log("trans.removeNoteAtSelected");
if (typeof selection == 'undefined') {
selection = trans.grid.getSelectedRange()
}
if (Boolean(selection) == false) {
console.log("no selection were made");
return false;
}
console.log(selection);
var minRow = Math.min(selection[0]['from']['row'], selection[0]['to']['row']);
var maxRow = Math.max(selection[0]['from']['row'], selection[0]['to']['row']);
var minCol = Math.min(selection[0]['from']['col'], selection[0]['to']['col']);
var maxCol = Math.max(selection[0]['from']['col'], selection[0]['to']['col']);
var hotComment = trans.grid.getPlugin('comments');
for (var y=minRow; y<=maxRow; y++) {
for (var x=minCol; x<=maxCol; x++) {
hotComment.removeCommentAtCell(y, x);
}
}
}
/**
* Draw grid's context menu to display translator list
*/
Trans.prototype.drawGridTranslatorMenu = function() {
if (!this.translatorContextMenuIsInitialized) {
console.log("Initializing translator context menu");
// init translatorContextMenu
TranslatorEngine.translators = TranslatorEngine.translators || {};
if (empty(TranslatorEngine.translators)) return;
this.gridContextMenu.translateUsing.submenu.items = [];
for (var id in TranslatorEngine.translators) {
(()=> {
var thisId = id;
var thisEngine = TranslatorEngine.translators[id];
this.gridContextMenu.translateUsing.submenu.items.push({
key: `translateUsing:${thisId}`,
name: thisEngine.name,
callback : ()=> {
this.translateSelection(undefined, {translatorEngine:thisEngine});
},
hidden : ()=> {
if (this.grid.isColumnHeaderSelected()) return true;
if (this.grid.isRowHeaderSelected()) return true;
}
})
})()
}
this.translatorContextMenuIsInitialized = true;
}
return this.gridContextMenu;
}
/**
* Get the context menu object of the grid
*/
Trans.prototype.getGridContextMenu = function() {
this.drawGridTranslatorMenu();
return this.gridContextMenu;
}
Trans.prototype.updateGridContextMenu = function(menu) {
console.log("updateGridContextMenu", menu);
if (!this.gridContextMenuIsModified) return;
}
Trans.prototype.modifyGridContextMenu = function(menu) {
this.gridContextMenuIsModified = true;
}
/**
* Asynchronously calculates the height of the table based on the provided data.
* @param {Array} [data=this.data] - The data to calculate the table height from. Defaults to the data stored in the Trans instance.
* @returns {Promise<number>} - A Promise that resolves with the calculated table height.
*/
Trans.prototype.calculateTableHeights = async function(data = this.data) {
console.log("Calculating table height");
const lineHeight = 23;
const padding = 2*lineHeight;
if (!data?.length) return lineHeight;
function calculateTableHeight(table) {
let totalLines = 0;
// Step 1: Find the maximum number of lines in any cell
for (let i = 0; i < table.length; i++) {
if (!table[i]?.length) continue;
let thisLength = 1;
for (let j = 0; j < table[i].length; j++) {
if (!table[i][j]) continue;
const lines = table[i][j].split('\n').length;
if (lines > thisLength) {
thisLength = lines;
}
}
totalLines += thisLength;
}
// Step 2: Calculate the height
if (totalLines < table.length) return table.length;
return totalLines;
}
if (data.length < 1000) {
return (calculateTableHeight(data) * lineHeight) + padding;
}
const Handler = require("www/js/CommonWorker.js").Handler
const workerData= {
command :"calculateHeights",
data : data,
options: {
logTarget: "ui"
}
}
const handler = new Handler("./www/js/trans.worker.js", workerData)
const result = await handler.getResult();
if (!result?.result) return lineHeight;
return (result.result * lineHeight) + padding;
}
/**
* Initialization of the grid
* @param {Object} options
*/
Trans.prototype.initTable = function(options) {
options = options||{};
//trans.removeDuplicates();
var container = document.getElementById('table');
if (Boolean(container) == false) return false;
Handsontable.dom.addEvent(container, 'blur', function(event) {
console.log("event", event);
});
Handsontable.debugLevel = common.debugLevel();
/**
* Instance of the Handsontable object
* @type {Handsontable}
* @see https://handsontable.com/docs/api/core/
*/
this.grid = new Handsontable(container, {
data : trans.data,
comments : true,
rowHeaders : true,
colHeaders : trans.colHeaders,
columns : trans.columns,
formulas : false,
search : true,
outsideClickDeselects:false,
viewportColumnRenderingOffset:15,
maxCols: Trans.maxCols,
//autoRowSize: true, // setting this to true will cause jumpy bugs
// dragToScroll:false,
// autoRowSize: {syncLimit: 1000},
autoRowSize: false,
//trimWhitespace : false,
//fixedColumnsLeft: 1,
minSpareRows : 1,
filters : false,
dropdownMenu : false,
autoWrapRow : true,
manualColumnMove: true,
//width: 806,
//height: 487,
manualColumnResize: true,
//copyPaste : true,
copyPaste: { columnsLimit: 15, rowsLimit: 100000 },
beforeChange: function (changes, source) {
console.log('beforeChange', arguments);
console.log(changes);
console.log(source);
// changes: [0] = row; [1]=col; [2]=initial value; [3]=changed value
if (typeof trans.selectedData == 'undefined') return console.warn("unknown selected data");
for (var i=0; i<changes.length; i++) {
// check if editing the key row
if (changes[i][1]==0 && changes[i][0] < (trans.grid.getData().length - 1)) {
console.log(JSON.stringify(changes, undefined, 2));
trans.alert(t("You should not edit key value"));
return false;
}
if (changes[i][1] != 0) continue; // skip if not first index
// reject if same key is found
if (Boolean(changes[i][3]) == false) return false;
if (trans.isKeyExistOn(changes[i][3])) {
trans.alert(t("Ilegal value")+" <b>'"+changes[i][3]+"'</b> "+t("That value already exist!"));
return false;
}
//if (typeof trans.findIdByIndex(changes[i][2]) != 'undefined') delete trans.indexIds[changes[i][2]];
if (typeof trans.findIdByIndex(changes[i][2]) == 'number') delete trans.indexIds[changes[i][2]];
//trans.indexIds[changes[i][3]] = changes[i][0];
trans.selectedData.indexIds[changes[i][3]] = changes[i][0];
}
},
afterChange: function (changes, source) {
//console.log('afterChange');
//console.log(changes);
//console.log(source);
// incoming changes is array;
// changes[index][0] = row; changes[index][1]=col;
// changes[index][2]=previous value; changes[index][3] = new value;
//console.log("afterChange");
//console.log(arguments);
trans.gridIsModified(true);
if (changes == null) return true;
if (!trans.getSelectedId()) return true;
var isChanged = false;
var progress = trans.project.files[trans.getSelectedId()].progress;
//var activeCellIndex = 0; //index changes which is active in preview
for (var cell in changes) {
if (Array.isArray(changes[cell]) == false) continue;
if (!trans.data[changes[cell][0]][0]) continue; // do not process if first row is blank or null
changes[cell][2]=changes[cell][2]||"";
changes[cell][3]=changes[cell][3]||"";
// detect current cell from array of changes
if (changes[cell][0] == trans.lastSelectedCell[0] && changes[cell][1] == trans.lastSelectedCell[1]) {
//activeCellIndex = cell;
trans.textEditorSetValue(changes[cell][3]);
}
if (changes[cell][2].length>0 && changes[cell][3].length==0) {
// removing
if (trans.countFilledCol(changes[cell][0]) == 0) {
// substracting progress
if (progress.translated <= 0) continue;
progress.translated --;
if (progress.length > 0) {
progress.percent = progress.translated/progress.length*100;
} else {
progress.percent = 0;
}
isChanged = true;
}
} else if (changes[cell][2].length==0 && changes[cell][3].length>0) {
// adding
if (trans.countFilledCol(changes[cell][0]) == 1) {
// if after adding a value, translation count in this row is exactly 1, than this is new translation for this row
// adding progress
if (progress.translated >= progress.length) continue;
progress.translated ++;
if (progress.length > 0) {
progress.percent = progress.translated/progress.length*100;
} else {
progress.percent = 0;
}
isChanged = true;
}
}
}
if (isChanged) {
var result ={};
result[trans.getSelectedId()] = progress;
trans.evalTranslationProgress(trans.getSelectedId(), result);
}
trans.trigger("afterCellChange", [changes, source]);
},
beforeContextMenuShow : (menu) => {
trans.updateGridContextMenu(menu)
},
contextMenu: {
items: trans.getGridContextMenu()
},
cells: function (row, col, prop) {
var cellProperties = {};
if (col==0) {
if (typeof trans.data[row] == 'undefined') return cellProperties;
if (trans.data[row][col] !== null && trans.data[row][col]!=="") {
cellProperties.readOnly = true;
}
}
return cellProperties;
},
afterSelection: function(row, column, row2, column2, preventScrolling, selectionLayerLevel) {
//HOT jumpy when selected, not caused by this
/**
* Triggered after grid selection
* @event Trans#beforeProcessSelection
* @param {arguments} arguments
*/
console.log("do after selection", arguments);
trans.trigger("beforeProcessSelection", arguments);
trans.doAfterSelection(row, column, row2, column2);
// tried this, not fixed the jumpy:
//preventScrolling.value = true;
},
beforeInit: function() {
console.log("running before init");
},
afterInit: function() {
trans.buildIndex();
},
beforeColumnMove: function(columns, target) {
console.log("beforeColumnMove");
if (target == 0) return false;
if (columns.includes(0) == true) return false;
/*
console.log(columns);
console.log(target);
trans.moveColumn(columns, target);
*/
trans.moveColumn(columns, target);
return true;
},
afterColumnMove: function(columns, target) {
console.log("afterColumnMove");
if (target == 0) return false;
if (columns.includes(0) == true) return false;
//console.log(columns);
//console.log(target);
//trans.walkToAllFile();
return true;
},
afterSetCellMeta: function(row, col, source, val){
if(source == 'comment'){
var thisData = trans.getSelectedObject();
if (!thisData) return true;
if (val == undefined) {
try {
delete thisData.comments[row][col];
}
catch(err) {
console.log("unable to delete comment.\nData row:"+row+", col:"+col+" is not exist on trans.project.files[trans.getCurrentID].comments!");
}
return true;
} else {
thisData.comments = thisData.comments||[];
thisData.comments[row] = thisData.comments[row]||[];
thisData.comments[row][col] = val.value;
return true;
}
}
},
afterRender:function(isForced) {
if (isForced == true) return true; // natural render
if ($("#currentCellText").is(":focus")) {
//console.log("eval focus");
var visibleCell = ui.getCurrentEditedCellElm();
if (visibleCell !== false) {
$("#table .currentCell").removeClass("currentCell");
visibleCell.addClass("currentCell");
}
}
},
afterRenderer:function(TD, row, column, prop, val, cellProperties) {
// fired after each cell rendered
// for performance do not put too much thing here
TD.setAttribute("data-coord", row+"-"+column);
if (column > 0) {
if (trans.getOption("gridInfo")?.isRuleActive) {
if (trans.getOption("gridInfo")?.viewOrganicCellMarker && trans.isOrganicCell(row, column)) {
$(TD).addClass("organic"); // organic is obviously viewed
} else if (trans.getOption("gridInfo")?.viewTrail && trans.isVisitedCell(row, column)) {
$(TD).addClass("viewed");
}
}
}
if (column > 0) return; // only render tag for the first column ... the rest is same
// draw last row
if (row >= trans.data.length - 1) $(TD).addClass("newKeyField");
//console.log($thisTR);
},
afterGetColHeader: function(col, TH) {
if (col==-1) $(TH).attr("data-role", "tablecorner")
},
afterGetRowHeader: function(row, TH) {
var $thisTH = $(TH)
var $thisTR = $(TH).closest("tr");
var $rowInfo = $thisTH.find(".rowInfo");
if (!$rowInfo.length) {
let $wrapper = $thisTH.find(">div");
$rowInfo = $('<span class="rowInfo"></span>').appendTo($wrapper)
}
//append info view element
(()=> {
// render tags
if (typeof trans.selectedData == 'undefined') return;
if (Array.isArray(trans.selectedData.tags) == false) return;
if (Array.isArray(trans.selectedData.tags[row]) == false) return;
if ($thisTR.hasClass("tagRendered") == true) return;
let shadowPart = [];
let borderOffset = 0;
for (var i=0; i<trans.selectedData.tags[row].length; i++) {
//console.log($thisTR.find("th"));
var segmTag = trans.selectedData.tags[row][i];
if (typeof consts.tagColor[segmTag] !== 'undefined') {
borderOffset+=consts.tagStripThickness;
shadowPart.push('inset '+borderOffset+'px 0px 0px 0px '+consts.tagColor[segmTag]);
}
$thisTH.addClass("tag-"+trans.selectedData.tags[row][i]);
}
if (shadowPart.length != 0) {
$thisTH.css("box-shadow", shadowPart.join(","));
}
$thisTR.addClass("hasTag");
$thisTR.addClass("tagRendered");
})()
// render context translation mark
if (ui.translationByContext) {
(()=> {
if (!trans.rowHasMultipleContext(row, trans.selectedData)) return;
$thisTH.addClass("hasMC")
if (ui.translationByContext.rowHasContextTranslation(row, trans.selectedData)) $thisTH.addClass("hasTC");
})()
}
// render row info
(()=>{
let rowInfoText = trans.getRowInfoText(row);
if (rowInfoText) {
$rowInfo.text(rowInfoText)
} else {
$rowInfo.text("")
}
})()
},
afterCreateRow:function(index, amount, source) {
var thisFile = trans.getSelectedId();
if (Boolean(thisFile) == false) return false;
trans.evalTranslationProgress([thisFile]);
},
beforePaste:function(data, coords) {
//console.log("beforePaste triggered");
//console.log(coords);
//console.log(arguments);
},
afterScrollVertically() {
// some part of the bottom most part are sometimes clipped, and no further scrolling is possible
// while some data are hidden beyound scrollable area
// this is the hackish solution for that.
// todo ... add 20% scroll ui.bumpGridScroll()
if (this.skipScrollEvent) return;
if (this._scrollTimer) clearTimeout(this._scrollTimer)
this._scrollTimer = setTimeout(async ()=> {
var elmHeight = $("#table .wtHolder>*:eq(0)").height();
if (elmHeight - $("#table .wtHolder").scrollTop() - $("#table .wtHolder").height() <= 0) {
//console.log("reached bottom");
if (!$(".newKeyField").visible()) {
this.render();
this.skipScrollEvent = true;
trans.grid.scrollViewportTo(ui.getFirstFullyVisibleRow());
await common.wait(50);
this.skipScrollEvent = false;
}
//this.loadData(trans.data);
//if (!$(".newKeyField").visible()) this.addHeight(400);
}
}, 200)
}
});
Handsontable.dom.addEvent($("#quickFind")[0], 'input', function (event) {
var search = trans.grid.getPlugin('search');
var queryResult = search.query(this.value);
console.log(queryResult);
trans.grid.render();
});
/**
* Handle row AutoRowSize plugin
* https://handsontable.com/docs/7.4.2/AutoRowSize.html
* Known problem: The grid will jumpy if row size is not complete when user try to click the grid.
*
*/
trans.grid.autoRowSize = trans.grid.getPlugin('autoRowSize');
// hot & walkOnTable custom hook
trans.grid.isFixedHeight = true;
trans.grid.view.wt.ignoreAdjustElementSize = false;
/**
* Saves row heights to the local storage for the specified file ID.
* @param {string} [fileId=trans.getSelectedId()] - The file ID to save row heights for.
* @returns {Promise<void>} - A Promise that resolves when the row heights are saved.
*/
const saveRowHeights = async function(fileId=trans.getSelectedId()) {
if (!trans.grid.getSettings().autoRowSize) return;
if (trans.grid.isFixedHeight) return;
if (trans.data?.length < 1000) return
console.log("Saving cache rowheights for ", fileId);
if (!trans?.localStorage) return;
console.log("Calculated row heights length ", trans.grid.autoRowSize.heights.length, "Current row length", trans.data.length);
if (trans.grid)
trans.localStorage.set(`${trans.getSelectedId()}?rowHeights`, trans.grid.autoRowSize.heights);
trans.localStorage.set(`${trans.getSelectedId()}?hiderDimension`, {
width:trans.grid.view.wt.wtTable.hider.style.width,
height:trans.grid.view.wt.wtTable.hider.style.height
})
}
// custom hooks
/**
* Loads row heights cache from local storage for the specified column range.
* @param {string} [colRange] - The column range to load row heights cache for.
* @returns {Promise<boolean>} - A Promise that resolves with a boolean indicating if the cache was loaded successfully.
*/
trans.grid.loadRowHeightsCache = async function(colRange) {
if (!trans.grid.getSettings().autoRowSize) return;
if (trans.grid.isFixedHeight) return;
if (trans.data?.length < 1000) return
console.log("start counting rows height on colRange", colRange)
if (!trans?.localStorage) return;
let cache = await trans.localStorage.get(`${trans.getSelectedId()}?rowHeights`);
console.log("Cache row height is", cache);
if (!cache?.length) return;
trans.grid.autoRowSize.heights = await trans.localStorage.get(`${trans.getSelectedId()}?rowHeights`);
trans.grid.autoRowSize.inProgress = false;
trans.grid.cachedDimension = await trans.localStorage.get(`${trans.getSelectedId()}?hiderDimension`);
// hooks on walkontable
// trans.grid.view.wt.getCachedDimension = function() {
// if (!trans.grid.cachedDimension) return;
// if (trans.grid.cachedDimension) {
// return trans.grid.cachedDimension;
// }
// }
return true;
}
trans.grid.onCalculateAllRowsHeightStart = async function() {
ui.tableCornerShowLoading("Counting total rows height. The grid may jumpy while in counting process.");
}
trans.grid.onCalculateAllRowsHeightEnd = async function() {
if (trans.data?.length < 1000) return
ui.tableCornerHideLoading();
// cache result
saveRowHeights();
}
trans.off("beforeSelectFile");
/**
* Event handler for the "beforeSelectFile" event.
* @param {Event} evt - The event object.
* @param {Array} fileIds - An array of file IDs.
*/
trans.on("beforeSelectFile", function(evt, fileIds) {
console.log("%cbeforeSelectFile", "color:pink", fileIds);
if (!fileIds?.[0]) return;
if (fileIds[0] == fileIds[1]) return; // no change made
saveRowHeights(fileIds[0]);
})
/**
* Sets the fixed table height based on the provided data.
* @param {Array} [data=[]] - The data to calculate the table height from.
* @returns {Promise<void>} - A Promise that resolves when the table height is set.
*/
trans.grid.setFixedTableHeightByData = async function(data = []) {
if (!trans.grid.isFixedHeight) return;
if (!data?.length) return;
// set fixed height first with a large enough data for responsiveness
// assume every row has 10 line
let initialHeight = data.length * 22 *10
const currentId = trans.getSelectedId();
trans.grid.setFixedTableHeight(initialHeight);
// begin counting the actual height
const calculatedHeight = await trans.calculateTableHeights(data);
if (trans.getSelectedId() !== currentId) return; // exit if the table has changed during processing
console.log("Setting actual height", calculatedHeight);
trans.grid.setFixedTableHeight(calculatedHeight);
}
/**
* Sets the fixed table height.
* @param {number|string|boolean} height - The height value. If a string, it should include "px".
* @returns {string|undefined} - The set height value, or undefined if height is false.
*/
trans.grid.setFixedTableHeight = function(height) {
if (!trans.grid.isFixedHeight) return;
if (typeof height == "string" && height.includes("px")) {
trans.grid.view.wt.wtTable.hider.style.height = height;
trans.grid.view.wt.ignoreAdjustElementSize = true;
} else if (typeof height == "number"){
trans.grid.view.wt.wtTable.hider.style.height = height+"px";
trans.grid.view.wt.ignoreAdjustElementSize = true;
} else if (typeof height == "boolean" && height === false) {
trans.grid.view.wt.ignoreAdjustElementSize = false;
}
return trans.grid.view.wt.wtTable.hider.style.height;
}
/**
* Adds height to the table.
* @param {number} num - The amount of height to add.
* @returns {number} - The new height after adding the specified amount.
*/
trans.grid.addHeight = function(num=0) {
const currentHeight = parseInt(trans.grid.view.wt.wtTable.hider.style.height);
trans.grid.view.wt.wtTable.hider.style.height = currentHeight+num+"px";
return trans.grid.view.wt.wtTable.hider.style.height;
}
/**
* Gets the height of the table.
* @returns {string} - The height of the table.
* @see https://handsontable.com/docs/7.4.2/Core.html#getTableHeight
* @since 4.7.15
* @example
* var tableHeight
* tableHeight
* // returns "500px"
*/
trans.grid.getTableHeight = function() {
return trans.grid.view.wt.wtTable.hider.style.height;
}
/**
* Check whether the current selection is selected through column header
* @returns {Boolean}
*/
trans.grid.isColumnHeaderSelected = function() {
return Boolean($(".htCore thead th.ht__active_highlight").length);
}
/**
* Checks if a row header is selected.
* @returns {boolean} - True if a row header is selected, false otherwise.
*/
trans.grid.isRowHeaderSelected = function() {
return Boolean($(".htCore tr th.ht__active_highlight").length);
}
/**
* Inserts a column to the right of the specified position.
* @param {string} [colName="New Col"] - The name of the new column.
* @param {number} [pos] - The position to insert the column. If not provided, the position will be selected from the currently selected cell.
* @returns {void} - This function does not return a value.
*/
trans.grid.insertColumnRight = function(colName, pos) {
if (trans.columns.length >= Trans.maxCols) {
alert(t("The maximum number of columns for this project has been reached, so you cannot add any more."))
return;
}
colName = colName||"New Col";
var getCol = pos||trans.grid.getSelected()[0][1];
var getColSet = trans.columns.length;
trans.columns.push({});
common.arrayExchange(trans.columns, getColSet, getCol + 1);
common.arrayInsert(trans.colHeaders, getCol+1, colName);
//batchArrayInsert(trans.data, getCol+1, null);
console.log(trans.columns);
trans.insertCell(getCol+1, null);
trans.grid.updateSettings({
colHeaders:trans.colHeaders
})
}
/**
* Sets the width of the specified column.
* @param {number} colIndex - The index of the column to set the width for.
* @param {number} newWidth - The new width value.
* @returns {void} - This function does not return a value.
*/
trans.grid.setColWidth = function(colIndex, newWidth) {
if (!trans.columns[colIndex]) return;
trans.columns[colIndex].width = newWidth;
trans.refreshGrid();
}
}
/*
hot2.updateSettings({
cells: function (row, col) {
var cellProperties = {};
if (hot2.getData()[row][col] === 'Nissan') {
cellProperties.readOnly = true;
}
return cellProperties;
}
});
*/
/**
* Find a key string, then insert a text into the adjacent cell
* Starting from 5.1 this function will strips carriage returns from `find`
* @param {String} find - String to find.
* @param {String} values - Text to put into
* @param {Number} columns - Column ID to put the values into
* @param {Object} indexes - index object or addresses
* @param {Object} options
* @param {Boolean} [options.overwrite=false] - Whether to overwrite the existing text on the destination cell or not
* @param {String[]} [options.files=this.getAllFiles()] - Selected files
* @param {Boolean} [options.keyColumn=0] - Key column id
* @param {Boolean} [options.lineByLine=false] - to set the search mode in line by line mode
* @since 4.7.15
*/
Trans.prototype.findAndInsertWithIndexes = function(find="", value, columns, indexes, options) {
//console.log("findAndInsertWithIndexes", arguments);
columns = columns||1;
options = options||{};
find ||= "";
options.overwrite = options.overwrite||false;
options.files = options.files||[];
options.keyColumn = options.keyColumn || this.keyColumn || 0;
options.insensitive = options.insensitive || false;
options.lineByLine = options.lineByLine || false;
options.customFilter;
indexes = indexes || this._tempIndexes;
if (typeof options.ignoreNewLine == "undefined") options.ignoreNewLine = true;
if (options.files.length == 0) { // that means ALL!
options.files = this.getAllFiles();
}
var result = {
keyword :find,
count :0,
executionTime:0,
files :{}
};
var indexObjects = this.getFromIndexes(find, indexes, options.customFilter);
//console.log("---indexObjects", indexObjects);
if (!indexObjects && find.includes("\r")) {
// RETRY: strip-out carriage returns
find = find.replaceAll("\r", "");
result.find = find;
indexObjects = this.getFromIndexes(find, indexes, options.customFilter);
}
if (!indexObjects) return result;
//console.log("reach here");
for (let i=0; i<indexObjects.length; i++) {
let thisAddress = indexObjects[i];
var file = thisAddress.file;
var row = thisAddress.row;
if (options.ignoreNewLine) {
if (Boolean(this.project.files[file].lineBreak) !== false) {
find = common.replaceNewLine(find, this.project.files[file].lineBreak);
}
}
// not support case insensitive
/*
if (thisAddress?.rowObj?.length) {
// the newest Address object directly point to row's memory address
console.log("Found rowObject", thisAddress?.rowObj);
} else
*/
if (typeof row == 'number') {
var thisRow = this.getData(file)[row];
if (!thisRow) continue;
if (options.filterTagMode == "blacklist") {
if (this.hasTags(options.filterTag, row, file)) continue;
} else if (options.filterTagMode == "whitelist") {
if (!this.hasTags(options.filterTag, row, file)) continue;
}
if (options.lineByLine) {
var currentText = thisRow[columns] || "";
var currentLines = currentText.replaceAll("\r", "").split("\n");
if (options.overwrite == false) {
if (Boolean(currentLines[thisAddress.line]) == true) continue;
}
currentLines[thisAddress.line] = value
thisRow[columns] = currentLines.join("\n");
} else {
// plain row by row
if (options.overwrite == false) {
if (Boolean(thisRow[columns]) == true) continue;
}
thisRow[columns] = value;
}
result.files[file] = result.files[file] || [];
result.files[file].push({
'fullString':thisRow[columns],
'row':row,
'col':columns,
'type':'cell',
'lineIndex':null
});
result.count++;
}
}
return result;
}
/**
* Find a key string, then insert a text into the adjacent cell
* @param {String} find - String to find.
* @param {String} values - Text to put into
* @param {Number} columns - Column ID to put the values into
* @param {Object} options
* @param {Boolean} [options.overwrite=false] - Whether to overwrite the existing text on the destination cell or not
* @param {String[]} [options.files=this.getAllFiles()] - Selected files
* @param {Boolean} [options.keyColumn=0] - Key column id
* @param {Boolean} [options.insensitive=false] - Whether the test is using insensitive case or not
*/
Trans.prototype.findAndInsert = function(find, values, columns, options) {
// find key "find", put "values" to coloumn with index "columns"
// to all files inside trans.project.files
//console.log("trans.findAndInsert", arguments);
columns = columns||1;
options = options||{};
options.overwrite = options.overwrite||false;
options.files = options.files||[];
options.keyColumn = options.keyColumn || this.keyColumn || 0;
options.insensitive = options.insensitive || false;
if (typeof options.ignoreNewLine == "undefined") options.ignoreNewLine = true;
//console.log("incoming parameters : ");
//console.log(arguments);
if (options.files.length == 0) { // that means ALL!
options.files = this.getAllFiles();
}
var result = {
keyword :find,
count :0,
executionTime:0,
files :{}
};
for (let i=0; i<options.files.length; i++) {
let file = options.files[i];
if (options.ignoreNewLine) {
if (Boolean(this.project.files[file].lineBreak) !== false) {
find = common.replaceNewLine(find, this.project.files[file].lineBreak);
}
}
var row
if (options.insensitive) {
row = this.getRowIdByTextInsensitive(find, file)
} else {
row = this.findIdByIndex(find, file);
}
if (typeof row == 'number') {
var thisRow = this.getData(file)[row];
if (!thisRow) continue;
if (options.filterTagMode == "blacklist") {
if (this.hasTags(options.filterTag, row, file)) continue;
} else if (options.filterTagMode == "whitelist") {
if (!this.hasTags(options.filterTag, row, file)) continue;
}
if (options.overwrite == false) {
if (Boolean(thisRow[columns]) == true) continue;
}
thisRow[columns] = values;
result.files[file] = result.files[file] || [];
result.files[file].push({
'fullString':thisRow[columns],
'row':row,
'col':columns,
'type':'cell',
'lineIndex':null
});
result.count++;
}
}
return result;
}
/**
* Find a key string, then insert a text into the adjacent cell using line-by-line algorithm
* @param {String} find - String to find.
* @param {String} values - Text to put into
* @param {Number} columns - Column ID to put the values into
* @param {Object} options
* @param {Boolean} [options.overwrite=false] - Whether to overwrite the existing text on the destination cell or not
* @param {String[]} [options.files=this.getAllFiles()] - Selected files
* @param {Boolean} [options.keyColumn=0] - Key column id
* @param {Boolean} [options.insensitive=false] - Whether the test is using insensitive case or not
* @param {String} [options.newLine=\n] - New line character
* @param {Boolean} [options.stripCarriageReturn=\n] - Whether to strip charriage return character (\r) or not
*/
Trans.prototype.findAndInsertLine = function(find, values, columns, options) {
// find key "find", put "values" to coloumn with index "columns"
// to all files inside trans.project.files
//console.log("findAndInsertLine:", arguments);
columns = columns||1;
options = options||{};
options.overwrite = options.overwrite||false;
options.files = options.files||[];
options.keyColumn = options.keyColumn||0;
options.newLine = options.newLine||undefined;
options.stripCarriageReturn = options.stripCarriageReturn||false;
if (options.stripCarriageReturn) {
find = common.stripCarriageReturn(find);
}
if (options.files.length == 0) { // that means ALL!
if (typeof trans.allFiles != 'undefined') {
options.files = trans.allFiles;
} else {
for (var file in trans.project.files) {
options.files.push(file);
}
trans.allFiles = options.files;
}
}
for (var i=0; i<options.files.length; i++) {
file = options.files[i];
var thisData = trans.project.files[file].data;
var thisNewLine = options.newLine||trans.project.files[file].lineBreak||"\n";
for (var row=0; row<thisData.length; row++) {
let keySegment
if (!thisData[row][options.keyColumn]) continue;
if (options.filterTagMode == "blacklist") {
if (this.hasTags(options.filterTag, row, file)) continue;
} else if (options.filterTagMode == "whitelist") {
if (!this.hasTags(options.filterTag, row, file)) continue;
}
thisData[row][options.keyColumn] = thisData[row][options.keyColumn]||"";
if (options.stripCarriageReturn) {
keySegment = thisData[row][options.keyColumn].replaceAll("\r", "").split("\n");
/*
var keySegment = thisData[row][options.keyColumn].split("\n").map(function(input) {
return common.stripCarriageReturn(input);
});
*/
} else {
keySegment = thisData[row][options.keyColumn].split("\n");
}
if (keySegment.includes(find) == false) continue;
thisData[row][columns] = thisData[row][columns]||"";
var targetSegment = thisData[row][columns].replaceAll("\r", "").split("\n");
/*
var targetSegment = thisData[row][columns].split("\n").map(function(input) {
return common.stripCarriageReturn(input);
});
*/
targetSegment = common.searchReplaceArray(keySegment, targetSegment, find, values, {overwrite:options.overwrite});
thisData[row][columns] = targetSegment.join(thisNewLine);
}
}
}
/**
* find key "find", put "values" to coloumn with index "columns"
* to all files inside trans.project.files
* @param {String} find - String to find.
* @param {String} values - Text to put into
* @param {Number} columns - Column ID to put the values into
* @param {Object} options
* @param {Boolean} [options.overwrite=false] - Whether to overwrite the existing text on the destination cell or not
* @param {String[]} [options.files=this.getAllFiles()] - Selected files
* @param {Boolean} [options.ignoreNewLine] - Whether to ignore new line characters or not
*/
Trans.prototype.findAndInsertByContext = function(find, values, columns, options) {
//console.log("trans.findAndInsertByContext:", arguments);
columns = columns||1;
options = options||{};
options.overwrite = options.overwrite||false;
options.files = options.files||[];
options.ignoreNewLine = options.ignoreNewLine||false;
if (options.files.length == 0) { // that means ALL!
options.files = this.getAllFiles();
}
for (let i=0; i<options.files.length; i++) {
let file = options.files[i];
var thisObj = this.getObjectById(file);
if (Boolean(thisObj) == false) continue;
if (Boolean(thisObj.context) == false) continue;
for (var row=0; row<thisObj.context.length; row++) {
var contextByRow = thisObj.context[row];
if (Array.isArray(contextByRow) == false) continue;
if (contextByRow.length < 1) continue;
if (Array.isArray(thisObj.data[row]) == false) continue;
for (var contextId=0; contextId<contextByRow.length; contextId++) {
if (find !== contextByRow[contextId]) continue;
// match found! assign value
if (options.overwrite == false) {
if (Boolean(thisObj.data[row][columns]) == true) continue;
}
thisObj.data[row][columns] = values;
}
}
}
}
/**
* Translate text with line-by-line algorithm
* @param {String} text - multilined text
* @param {Object} translationPair - Key pair translation object
*/
Trans.prototype.translateTextByLine = function(text, translationPair, options) {
/*
translating text line by line by translationPair
text : multilined text
translationPair: object : {"key":"translation"}
output: translated text
todo : make line break type match original text
*/
//console.log("trans.translateTextByLine", arguments);
if (typeof text !== 'string') return text;
if (text.length < 1) return text;
// Up to ver 3.8.21 blank text will returns original text
//var translated = text;
var translated = "";
var translatedLine = [];
var lines = text.replace(/(\r\n)/gm, "\n").split("\n");
//var lines = text.split("\n");
//console.log("lines are:", lines);
//console.log("%c-With translation pair:", "color:#0f0;", JSON.stringify(translationPair, undefined, 2))
for (var i=0; i<lines.length; i++) {
var line = lines[i];
//console.log("%c--Comparing:", "color:#0f0;", JSON.stringify(line))
if (translationPair[line]) {
translatedLine.push(translationPair[line]);
//console.log("%c--found", "color:#0f0;")
continue;
}
//console.log("%c--not found", "color:#0f0;")
// Up to ver 3.8.21 blank text will returns original text
//translatedLine.push(line);
translatedLine.push("");
}
//console.log("translatedLine:", translatedLine);
translated = translatedLine.join("\n");
//console.log("Result of trans.translateTextByLine", translated);
return translated;
}
/**
* translate from array obj
* @param {*} obj
* @param {Number} columns
* @param {Object} options
* @param {Number|'auto'} options.sourceColumn
* @param {Boolean} [options.overwrite=false] - Whether to overwrite the existing text on the destination cell or not
* @param {Number} [options.sourceKeyColumn=0] - The source key column
* @param {String[]} [options.files=this.getAllFiles()] - Selected files
* @param {Boolean} [options.keyColumn=0] - Key column id
* @param {String} [options.newLine=\n] - New line character
* @param {Boolean} [options.stripCarriageReturn=\n] - Whether to strip charriage return character (\r) or not
*/
Trans.prototype.translateFromArray = function(obj, columns, options) {
// translate from array obj
console.log("insert trans.translateFromArray", arguments);
columns = columns||1;
options = options||{};
options.sourceColumn = options.sourceColumn||"auto";
options.overwrite = options.overwrite||false;
options.files = options.files||[];
options.sourceKeyColumn = options.sourceKeyColumn||0;
options.keyColumn = options.keyColumn||0;
options.newLine = options.newLine||undefined;
options.stripCarriageReturn = options.stripCarriageReturn||false;
options.ignoreNewLine = true; // let's set to true;
//options.translationMethod = options.translationMethod||false;
obj = obj||[];
if (obj.length == 0) return false;
if (!options.indexes) {
if (options.files?.length == 0) {
options.files = this.getAllFiles();
}
options.indexes = this.buildIndexes(options.files, false);
}
for (var rowId=0; rowId<obj.length; rowId++) {
var translation;
var row = obj[rowId];
if (Boolean(row[options.sourceKeyColumn]) == false) continue;
var keyString = row[options.sourceKeyColumn];
if (options.sourceColumn == 'auto') {
translation = trans.getTranslationFromRow(row, options.sourceKeyColumn);
} else {
translation = row[options.sourceColumn];
}
//trans.findAndInsert = function(find, values, columns, options)
//trans.findAndInsert(keyString, translation, columns, options);
trans.findAndInsertWithIndexes(
keyString,
translation,
columns,
options.indexes,
{
overwrite :options.overwrite,
files :options.files
});
}
}
/**
* Abort translation process
*/
Trans.prototype.abortTranslation = function() {
trans.translator = trans.translator||[];
if (typeof trans.translationTimer !== 'undefined') {
clearTimeout(trans.translationTimer);
}
for (var i=0; i<trans.translator.length; i++) {
let translator = trans.translator[i];
var thisTranslator = trans.getTranslatorEngine(translator)
if (!thisTranslator) continue;
if (typeof thisTranslator.abort == "function") {
try {
thisTranslator.abort();
} catch (e) {
ui.log("Failed to abort with message", e.toString());
}
}
thisTranslator.job = thisTranslator.job||{};
thisTranslator.job.wordcache = {};
thisTranslator.job.batch = [];
}
ui.hideLoading();
trans.refreshGrid();
trans.evalTranslationProgress();
}
/**
* Translate string by translation pair
* @param {String} str - String to translate
* @param {Object} translationPair - Key value based translation pair object
* @param {Boolean} caseInSensitive - Whether or not the translation is in case insensitive mode
* @returns {String} Translated result
*/
Trans.prototype.translateStringByPair = function(str, translationPair, caseInSensitive) {
caseInSensitive = caseInSensitive||false;
if (typeof str !== 'string') return str;
if (typeof translationPair !== 'object') return str;
for (var key in translationPair) {
str = str.replaces(key, translationPair[key], caseInSensitive);
}
return str;
}
/**
* Search a translation from given trans data
* @param {String|String[]} text - Original text to translate
* @param {String|String[]} [fileFilter] - Destination file ID to search into. If blank then the method will search all file object
* @param {Trans} [transData=this] - Trans Data
* @since 6.1.14
* @returns {String|undefined} - translation result
*/
Trans.prototype.translateFromTrans = function(text, fileFilter = [], transData = this) {
if (!text) return "";
if (typeof fileFilter == "string") fileFilter = [fileFilter];
const files = transData?.project?.files || {};
if (!fileFilter?.length) {
fileFilter = this.getAllFiles(files);
}
const getInstance = (text) => {
for (let i in fileFilter) {
let fileId = fileFilter[i]
let index = this.getIndexByKey(fileId, text);
if (typeof index == "undefined") continue;
let data = this.getData(fileId);
if (empty(data[index])) return "";
let translation = this.getTranslationFromRow(data[index])
if (translation) return translation;
}
}
if (Array.isArray(text)) {
let result = []
for (let i in text) {
result[i] = getInstance(text[i]) || ""
}
return result;
} else {
return getInstance(text);
}
}
/**
* Search a string from reference, will return blank if not found.
* @param {String} string - String to search for
* @param {String} [file=Common Reference] - File to be used as reference
* @returns {String} Translated text, blank if no reference found.
*/
Trans.prototype.getReference = function(string, file = "Common Reference") {
if (!string) return "";
var index = this.getIndexByKey(file, string);
if (typeof index == "undefined") return "";
var data = this.getData(file);
if (empty(data[index])) return "";
return this.getTranslationFromRow(data[index]) || "";
}
/**
* Get translation from common reference. The behavior is like translation procedure.
* Will return original text if no reference found
* @param {String} input - String to be translated
* @param {Boolean} [caseInSensitive=false] - Whether to perform case insensitive search or not
* @param {String} [referenceName="Common Reference"] - The name of the reference file object
* @returns {String} translated string
*/
Trans.prototype.translateByReference = function(input, caseInSensitive, referenceName="Common Reference") {
caseInSensitive = caseInSensitive||false;
//data = JSON.parse(JSON.stringify(input));
//console.log("Reference", referenceName);
if (!trans.project.files[referenceName]) return input;
trans.project.files[referenceName]["cacheResetOnChange"] = trans.project.files[referenceName]["cacheResetOnChange"]||{};
var transPair;
if (Boolean(trans.project.files[referenceName]["cacheResetOnChange"]["transPair"])!== false) {
//console.log("load translation pair from cache");
transPair = trans.project.files[referenceName]["cacheResetOnChange"]["transPair"];
} else {
transPair = trans.generateTranslationPair(trans.project.files[referenceName].data);
trans.project.files[referenceName]["cacheResetOnChange"]["transPair"] = transPair;
}
//console.log("Translate by reference pair :", transPair);
var output
if (typeof input == 'string') {
output = trans.translateStringByPair(input, transPair, caseInSensitive);
return output;
} else if (Array.isArray(input)){
output = [];
for (var i=0; i<input.length; i++) {
output[i] = trans.translateStringByPair(input[i], transPair, caseInSensitive);
}
return output;
}
return input;
}
/**
* Check whether a row has translation or not
* @param {String[]} row - Single dimensional array representing rows
* @param {Number} [keyColumn=0]
* @returns {Boolean} True if has translation
*/
Trans.prototype.rowHasTranslation = function(row, keyColumn) {
keyColumn = this.keyColumn || 0;
if (empty(row)) return false;
for (var i=0; i<row.length; i++) {
if (i == keyColumn) continue;
if (row[i]) return true;
}
return false;
}
/**
* Execute batch translation and automatically detect translation mode
* @param {TranslatorEngine} translator - Selected Translator engine object
* @param {Object} options
* @param {Function} [options.onFinished] - Function to run when the process completed
* @param {Number} [options.keyColumn=0] - Key column index of the table
* @param {Boolean} [options.translateOther] - Whether to translate unselected files or not
* @param {Boolean} [options.saveOnEachBatch] - Whether to save project for each batch
* @param {String[]} [options.filterTag] - Tags filter
* @param {'blacklist'|'whitelist'} [options.filterTagMode] - Filter mode
* @param {Boolean} [options.overwrite=false] - Whether to overwrite or not if the destination cell is not empty
* @param {Boolean} [options.ignoreTranslated=false] - Whether to skip processing or not if the row already has translation
*/
Trans.prototype.translateAll = function(translator, options) {
var activeTranslator = this.getTranslatorEngine(translator);
this.buildIndex("Common Reference", true);
if (activeTranslator.mode == "rowByRow") {
trans.translateAllByRows(translator, options);
} else {
trans.translateAllByLines(translator, options);
}
}
/**
* Execute batch translation with row by row mode
* @param {TranslatorEngine} translator - Selected Translator engine object
* @param {Object} options
* @param {Function} [options.onFinished] - Function to run when the process completed
* @param {Number} [options.keyColumn=0] - Key column index of the table
* @param {Boolean} [options.translateOther] - Whether to translate unselected files or not
* @param {Boolean} [options.saveOnEachBatch] - Whether to save project for each batch
* @param {String[]} [options.filterTag] - Tags filter
* @param {'blacklist'|'whitelist'} [options.filterTagMode] - Filter mode
* @param {Boolean} [options.overwrite=false] - Whether to overwrite or not if the destination cell is not empty
* @param {Boolean} [options.ignoreTranslated=false] - Whether to skip processing or not if the row already has translation
*/
Trans.prototype.translateAllByRows = async function(translator, options) {
// TRANSLATE ALL
console.log("Running trans.translateAll", options);
ui.loadingProgress(0, "Running trans.translateAll");
var thisTranslator = this.getTranslatorEngine(translator);
if (typeof trans.project == 'undefined') return trans.alert(t("Unable to process, project not found"));
if (typeof trans.project.files == 'undefined') return trans.alert(t("Unable to process, data not found"));
if (typeof thisTranslator == 'undefined') return trans.alert(t("Translation engine not found"));
if (thisTranslator.isDisabled) return trans.alert(t("Translation engine ")+translator+t(" is disabled!"));
if (!trans.getSelectedId()) {
trans.selectFile($(".fileList .data-selector").eq(0));
}
options = options||{};
options.onFinished = options.onFinished||function() {};
options.keyColumn = options.keyColumn||0;
options.translateOther = options.translateOther|| false;
options.ignoreTranslated= options.ignoreTranslated || false;
options.overwrite = options.overwrite || false;
options.saveOnEachBatch = options.saveOnEachBatch || false;
// COLLECTING DATA
thisTranslator.job = thisTranslator.job||{};
thisTranslator.job.wordcache = {};
thisTranslator.job.batch = [];
thisTranslator.batchDelay = thisTranslator.batchDelay||trans.config.batchDelay;
// CALCULATING max request length
var currentMaxLength = trans.config.maxRequestLength;
if (thisTranslator.maxRequestLength < currentMaxLength) currentMaxLength = thisTranslator.maxRequestLength;
// SHOW loading bar
ui.showLoading({
buttons:[{
text:"Abort",
onClick: function(e) {
var sure = confirm(t("Are you sure want to abort this process?"));
if (sure) trans.abortTranslation();
}
},
{
text:"Pause",
onClick: function(e) {
alert(t("Process paused!\nPress OK to continue!"));
}
}
]
});
ui.log("Start batch translation in Row by Row mode, with options:", options);
ui.log(`Translator is: ${translator}`);
console.log("Current maximum request length : "+currentMaxLength);
console.log("Start collecting data!");
ui.loadingProgress(0, "Start collecting data!");
var currentBatchID = 0;
var currentRequestLength = 0;
// to store key and source column pair when source column !=0;
var keyIndex = {};
// collecting selected row
var selectedFiles = trans.getCheckedFiles();
ui.loadingProgress(undefined, t("Selected files :")+selectedFiles.join(", "));
// none selected means all
if (selectedFiles.length == 0) selectedFiles = this.getAllFiles();
for (var thisIndex in selectedFiles) {
var file = selectedFiles[thisIndex];
ui.loadingProgress(undefined, t("Collecting data from :")+file);
try {
var currentData = trans.project.files[file].data;
} catch(e) {
console.warn("Can not access", file, "in trans.project.files" );
continue;
}
if (typeof thisTranslator.job.batch[currentBatchID] == 'undefined') thisTranslator.job.batch[currentBatchID] = [];
for (var i=0; i< currentData.length; i++) {
// skip if col[0] is empty, regardless keyColum, because we will use that latter
if (!currentData[i][0]) continue;
var currentSentence = currentData[i][options.keyColumn];
var escapedSentence = str_ireplace($DV.config.lineSeparator, thisTranslator.lineSubstitute, thisTranslator.escapeCharacter(currentSentence));
// skiping when empty
if (!currentSentence) continue;
// skip according to tags
if (options.filterTagMode == "blacklist") {
if (this.hasTags(options.filterTag, i, file)) continue;
} else if (options.filterTagMode == "whitelist") {
if (!this.hasTags(options.filterTag, i, file)) continue;
}
// skip line that already translated
if (options.ignoreTranslated) {
if (this.rowHasTranslation(currentData[i], options.keyColumn)) continue;
}
// skip line if cell is not empty & overwrite option is false
if (options.overwrite == false) {
if (currentData[i][thisTranslator.targetColumn]) {
console.log("Ignored because overwite options", options.overwrite, currentData[i][thisTranslator.targetColumn]);
continue;
}
}
if (currentSentence.trim().length == 0) continue;
// assign keyIndex pair
keyIndex[currentSentence] = currentData[i][0];
if (escapedSentence.length > currentMaxLength) {
console.log('current sentence is bigger than maxRequestLength!');
currentBatchID++;
thisTranslator.job.batch[currentBatchID] = thisTranslator.job.batch[currentBatchID]||[];
thisTranslator.job.batch[currentBatchID].push(currentSentence);
currentBatchID++;
currentRequestLength = 0;
continue;
}
//if (currentSentence.length+currentRequestLength > currentMaxLength) {
if (escapedSentence.length+currentRequestLength > currentMaxLength) {
currentBatchID++;
thisTranslator.job.batch[currentBatchID] = thisTranslator.job.batch[currentBatchID]||[];
thisTranslator.job.batch[currentBatchID].push(currentSentence);
currentRequestLength = 0;
continue;
}
// make each line unique
if (typeof thisTranslator.job.wordcache[currentSentence] !== 'undefined') {
continue;
} else {
thisTranslator.job.wordcache[currentSentence] = true;
}
thisTranslator.job.batch[currentBatchID] = thisTranslator.job.batch[currentBatchID]||[];
thisTranslator.job.batch[currentBatchID].push(currentSentence);
//currentRequestLength += currentSentence.length;
currentRequestLength += escapedSentence.length;
}
}
await ui.log("Generating indexes");
var tempIndexes = this.buildIndexes(selectedFiles);
thisTranslator.job.batchLength = thisTranslator.job.batch.length;
console.log("current batch:", thisTranslator.job.batch);
ui.loadingProgress(0, "Data collection is done!");
console.log("Data collection is done!");
console.log("We have "+thisTranslator.job.batch.length+" batch totals!");
console.log("==========================================");
console.log("Begin translating using "+translator+"!");
var processPart = async function() {
if (typeof thisTranslator.job.batch == 'undefined') return "batch job undefined, quiting!";
if (thisTranslator.job.batch.length < 1) {
console.log("Batch job is finished");
trans.refreshGrid();
trans.evalTranslationProgress();
if (typeof options.onFinished == 'function') {
options.onFinished.call(this);
}
ui.loadingProgress(100, t("Translation finished"));
//ui.hideLoading();
ui.loadingClearButton();
ui.loadingEnd();
trans.onBatchTranslationDone(options);
trans.translationTimer = undefined;
return "batch job is finished!";
}
console.log("running processPart");
var currentData = thisTranslator.job.batch.pop();
console.log("current data : ");
console.log(currentData);
var preTransData;
if (thisTranslator.skipReferencePair) {
preTransData = currentData;
} else {
preTransData = trans.translateByReference(currentData);
}
thisTranslator.translate(preTransData, {
mode: "rowByRow",
onAfterLoading:async function(result) {
console.log(result);
if (typeof result.translation !== 'undefined') {
console.log("applying translation to table !");
console.log("calculating progress");
var percent = thisTranslator.job.batch.length/thisTranslator.job.batchLength*100;
percent = 100-percent;
ui.loadingProgress(percent, t("applying translation to table!"));
var targetFiles = selectedFiles;
if (options.translateOther) targetFiles = trans.getAllFiles();
trans.trigger("batchTranslationResult", {original:currentData, translation:result.translation, translator:thisTranslator})
for (var index in result.translation) {
//trans.findAndInsert(keyIndex[currentData[index]],
trans.findAndInsertWithIndexes(
keyIndex[currentData[index]],
result.translation[index],
thisTranslator.targetColumn,
tempIndexes,
{
filterTag : options.filterTag || [],
filterTagMode : options.filterTagMode,
keyColumn : options.keyColumn,
overwrite : options.overwrite,
files : targetFiles
});
}
if (options.saveOnEachBatch) {
ui.log("Saving your project");
await trans.save();
}
ui.loadingProgress(undefined, (thisTranslator.job.batchLength-thisTranslator.job.batch.length)+"/"+thisTranslator.job.batchLength+" batch done, waiting "+thisTranslator.batchDelay+" ms...");
trans.translationTimer = setTimeout(function(){ processPart(); }, thisTranslator.batchDelay);
}
//trans.refreshGrid();
//trans.grid.render();
},
onError:function(evt, type, errorType) {
console.log("ERROR on transling data");
var percent = thisTranslator.job.batch.length/thisTranslator.job.batchLength*100;
percent = 100-percent;
ui.loadingProgress(percent,
t("Error when translating data!")+"\r\n"+
t("\tHTTP Status : ")+evt.status+"\r\n"+
t("\tError Type : ")+errorType+"\r\n"+
t("You are probably temporarily banned by your translation service!\r\nPlease use online translation service in moderation\r\nThis usualy fixed by itself within a day or two.\r\n"));
ui.loadingProgress(undefined, (thisTranslator.job.batchLength-thisTranslator.job.batch.length)+"/"+thisTranslator.job.batchLength+t(" batch done, ")+t("waiting ")+thisTranslator.batchDelay+t(" ms..."));
trans.translationTimer = setTimeout(function(){ processPart(); }, thisTranslator.batchDelay);
}
});
}
processPart();
}
/**
* Execute batch translation with line-by-line mode
* @param {TranslatorEngine} translator - Selected Translator engine object
* @param {Object} options
* @param {Function} [options.onFinished] - Function to run when the process completed
* @param {Number} [options.keyColumn=0] - Key column index of the table
* @param {Boolean} [options.translateOther] - Whether to translate unselected files or not
* @param {Boolean} [options.saveOnEachBatch] - Whether to save project for each batch
* @param {String[]} [options.filterTag] - Tags filter
* @param {'blacklist'|'whitelist'} [options.filterTagMode] - Filter mode
* @param {Boolean} [options.overwrite=false] - Whether to overwrite or not if the destination cell is not empty
* @param {Boolean} [options.ignoreTranslated=false] - Whether to skip processing or not if the row already has translation
*/
Trans.prototype.translateAllByLines = async function(translator, options) {
// TRANSLATE ALL
console.log("Running trans.translateAllByLines", arguments);
ui.loadingProgress(0, t("Running trans.translateAll"));
var thisTranslator = this.getTranslatorEngine(translator);
if (typeof trans.project == 'undefined') return trans.alert(t("Unable to process, project not found"));
if (typeof trans.project.files == 'undefined') return trans.alert(t("Unable to process, data not found"));
if (typeof thisTranslator == 'undefined') return trans.alert(t("Translation engine not found"));
if (thisTranslator.isDisabled) return trans.alert(t("Translation engine ")+translator+t(" is disabled!"));
if (!trans.getSelectedId()) {
trans.selectFile($(".fileList .data-selector").eq(0));
}
options = options||{};
options.onFinished = options.onFinished||function() {};
options.keyColumn = options.keyColumn||0;
options.translateOther = options.translateOther|| false;
options.ignoreTranslated= options.ignoreTranslated || false;
options.overwrite = options.overwrite || false;
options.saveOnEachBatch = options.saveOnEachBatch || false;
var fetchMode = "both"
if (options.ignoreTranslated) {
fetchMode = "untranslated"
}
// COLLECTING DATA
thisTranslator.job = thisTranslator.job||{};
thisTranslator.job.wordcache = {};
thisTranslator.job.batch = [];
thisTranslator.batchDelay = thisTranslator.batchDelay||trans.config.batchDelay;
// CALCULATING max request length
var currentMaxLength = trans.config.maxRequestLength;
if (thisTranslator.maxRequestLength < currentMaxLength) currentMaxLength = thisTranslator.maxRequestLength;
// SHOW loading bar
ui.showLoading({
buttons:[{
text:"Abort",
onClick: function(e) {
var sure = confirm(t("Are you sure want to abort this process?"));
if (sure) trans.abortTranslation();
}
},
{
text:"Pause",
onClick: function(e) {
alert(t("Process paused!\nPress OK to continue!"));
}
}
]
});
ui.log("Start batch translation in Line by Line mode, with options:", options);
ui.log(`Translator is: ${translator}`);
console.log("Current maximum request length : "+currentMaxLength);
console.log("Start collecting data!");
ui.loadingProgress(0, t("Start collecting data!"));
var currentBatchID = 0;
var currentRequestLength = 0;
// collecting selected row
var selectedFiles = trans.getCheckedFiles();
ui.loadingProgress(undefined, t("Selected files :")+selectedFiles.join(", "));
var transTableInfo = trans.generateTranslationTableLine(trans.project, {
files : selectedFiles,
mode : "lineByLine",
fetch : fetchMode,
keyColumn : options.keyColumn,
filterLanguage : this.getSl(),
filterTag : options.filterTag || [],
filterTagMode : options.filterTagMode,
ignoreTranslated : options.ignoreTranslated,
targetColumn : thisTranslator.targetColumn,
overwrite : options.overwrite,
ignoreWhitespace : thisTranslator.ignoreWhitespace,
collectAddress : true
});
var transTable = transTableInfo.pairs;
console.log("Fetch mode : ", fetchMode);
console.log("Translatable : ", transTable);
currentBatchID = 0;
currentRequestLength = 0;
for (var translateKey in transTable) {
thisTranslator.job.batch[currentBatchID] = thisTranslator.job.batch[currentBatchID]||[];
var currentSentence = translateKey;
var escapedSentence = str_ireplace($DV.config.lineSeparator, thisTranslator.lineSubstitute, thisTranslator.escapeCharacter(currentSentence));
// skiping when empty
if (Boolean(currentSentence) == false) continue;
if (typeof currentSentence !== 'string') continue;
if (currentSentence.trim().length == 0) continue;
if (escapedSentence.length > currentMaxLength) {
console.log('current sentence is bigger than maxRequestLength!');
currentBatchID++;
thisTranslator.job.batch[currentBatchID] = thisTranslator.job.batch[currentBatchID]||[];
thisTranslator.job.batch[currentBatchID].push(currentSentence);
currentBatchID++;
currentRequestLength = 0;
continue;
}
if (escapedSentence.length+currentRequestLength > currentMaxLength) {
currentBatchID++;
thisTranslator.job.batch[currentBatchID] = thisTranslator.job.batch[currentBatchID]||[];
thisTranslator.job.batch[currentBatchID].push(currentSentence);
currentRequestLength = 0;
continue;
}
thisTranslator.job.batch[currentBatchID] = thisTranslator.job.batch[currentBatchID]||[];
thisTranslator.job.batch[currentBatchID].push(currentSentence);
//currentRequestLength += currentSentence.length;
currentRequestLength += escapedSentence.length;
}
// cleaning up transTable for RAM friendly
transTable = undefined;
thisTranslator.job.batchLength = thisTranslator.job.batch.length;
ui.loadingProgress(0, t("Data collection is done!"));
console.log("Data collection is done!");
console.log("We have "+thisTranslator.job.batch.length+" batch totals!");
console.log("==========================================");
console.log("Begin translating using "+translator+"!");
await ui.log("Generating indexes");
var tempIndexes = this.buildIndexes(selectedFiles, true);
console.log("indexes:", tempIndexes);
var processPart = async function() {
if (typeof thisTranslator.job.batch == 'undefined') return "batch job undefined, quiting!";
if (thisTranslator.job.batch.length < 1) {
console.log("Batch job is finished");
// filling skipped translation
var selectedObj = [];
if (options.translateOther == false) selectedObj = trans.getCheckedFiles();
trans.fillEmptyLine(selectedObj, [], thisTranslator.targetColumn, options.keyColumn, {
lineFilter : function(str) {
return !common.isInLanguage(str, trans.getSl());
},
fromKeyOnly : options.ignoreTranslated,
filterTag : options.filterTag || [],
filterTagMode : options.filterTagMode,
overwrite : options.overwrite
});
trans.refreshGrid();
trans.evalTranslationProgress();
if (typeof options.onFinished == 'function') {
options.onFinished.call(this);
}
ui.loadingProgress(100, t("Translation finished"));
//ui.hideLoading();
ui.loadingClearButton();
ui.loadingEnd();
trans.onBatchTranslationDone(options);
trans.translationTimer = undefined;
return "batch job is finished!";
}
var translatePart = async function() {
console.log("running processPart");
var selectedObj = [];
if (options.translateOther == false) selectedObj = trans.getCheckedFiles();
trans.fillEmptyLine(selectedObj, [], thisTranslator.targetColumn, options.keyColumn, {
lineFilter : function(str) {
return !common.isInLanguage(str, trans.getSl());
},
fromKeyOnly : options.ignoreTranslated,
filterTag : options.filterTag || [],
filterTagMode : options.filterTagMode,
overwrite : options.overwrite
});
var currentData = thisTranslator.job.batch.pop();
console.log("current data : ");
console.log(currentData);
var preTransData;
if (thisTranslator.skipReferencePair) {
preTransData = currentData;
} else {
preTransData = trans.translateByReference(currentData);
}
thisTranslator.translate(preTransData, {
onAfterLoading:async function(result) {
console.log("onAfterLoading translation result:", result);
if (typeof result.translation !== 'undefined') {
console.log("applying translation to table !");
//console.log("calculating progress");
var percent = thisTranslator.job.batch.length/thisTranslator.job.batchLength*100;
percent = 100-percent;
ui.loadingProgress(percent, t("applying translation to table!"));
trans.trigger("batchTranslationResult", {original:currentData, translation:result.translation, translator:thisTranslator})
for (var index in result.translation) {
trans.findAndInsertWithIndexes(
currentData[index],
result.translation[index],
thisTranslator.targetColumn,
//tempIndexes,
transTableInfo.addresses,
{
files : selectedObj,
keyColumn : options.keyColumn,
stripCarriageReturn : true,
filterTag : options.filterTag || [],
filterTagMode : options.filterTagMode,
overwrite : options.overwrite,
lineByLine : true
});
}
if (options.saveOnEachBatch) {
ui.log("Saving your project");
await trans.save();
}
//ui.loadingProgress(undefined, (thisTranslator.job.batchLength-thisTranslator.job.batch.length)+"/"+thisTranslator.job.batchLength+" batch done, waiting "+thisTranslator.batchDelay+" ms...");
ui.loadingProgress(undefined, (thisTranslator.job.batchLength-thisTranslator.job.batch.length)+"/"+thisTranslator.job.batchLength+" batch done!");
processPart();
}
},
onError:function(evt, type, errorType) {
console.log("ERROR on transling data");
var percent = thisTranslator.job.batch.length/thisTranslator.job.batchLength*100;
percent = 100-percent;
ui.loadingProgress(percent,
t("Error when translating data!")+"\r\n"+
t("\tHTTP Status : ")+evt.status+"\r\n"+
t("\tError Type : ")+errorType+"\r\n"+
t("You are probably temporarily banned by your translation service!\r\nPlease use online translation service in moderation\r\nThis usualy fixed by itself within a day or two.\r\n"));
ui.loadingProgress(undefined, (thisTranslator.job.batchLength-thisTranslator.job.batch.length)+"/"+thisTranslator.job.batchLength+" batch done!");
processPart();
}
});
}
if (thisTranslator.job.batchLength == thisTranslator.job.batch.length) {
translatePart();
} else {
ui.loadingProgress(undefined, "Waiting "+thisTranslator.batchDelay+" ms...");
trans.translationTimer = setTimeout(function(){ translatePart(); }, thisTranslator.batchDelay);
}
}
processPart();
}
/**
* Function to run when translation is completed
* @param {Object} options The same options with trans.translateAll function
*/
Trans.prototype.onBatchTranslationDone = function(options) {
if (options.playSoundOnComplete) {
var myAudioElement = new Audio('/www/audio/done.mp3');
myAudioElement.addEventListener("canplay", event => {
/* the audio is now playable; play it if permissions allow */
myAudioElement.play();
});
}
}
/**
* Get translator engine object by its ID
* @param {String} id - The ID of translator engine
* @returns {TranslatorEngine} The translator engine
*/
Trans.prototype.getTranslatorEngine = function(id) {
if (TranslatorEngine.translators[id]) return TranslatorEngine.translators[id];
if (this[id] instanceof TranslatorEngine) return this[id];
console.warn(id+" is not a translator engine");
}
/**
* Get currently active Translator Engine
*/
Trans.prototype.getActiveTranslatorEngine = function() {
var doInitLang = function() {
var conf = confirm(t("Source & target language and the default translator engine is not yet defined for this project.\r\nPlease set it up in option menu!\r\nDo you want to set this options now?"));
if (conf) ui.openOptionsWindow();
}
this.project.options = this.project.options || {};
var currentPlugin = this.project.options.translator||sys.config.translator;
if (typeof currentPlugin == 'undefined') return doInitLang();
return this.getTranslatorEngine(currentPlugin);
}
/**
* Determines translate selection by row or by line
* @param {CellRange[]} [currentSelection=trans.grid.getSelected()] - Cell range to be translated
* @param {Object} options
* @param {Object} [options.translatorEngine=this.getActiveTranslatorEngine()] - Translator engine to be used
*/
Trans.prototype.translateSelection = async function(currentSelection, options = {}) {
//var activeTranslator = options.translatorEngine || this.getActiveTranslatorEngine();
this.buildIndex("Common Reference", true);
var trans = this;
currentSelection = currentSelection||trans.grid.getSelectedRange()||[[]];
if (typeof currentSelection == 'undefined') {
alert(t("nothing is selected"));
return false;
}
if (typeof trans.translator == "undefined" || trans.translator.length < 1) {
alert(t("no translator loaded"));
return false;
}
var currentEngine = options.translatorEngine || this.getActiveTranslatorEngine();
options.mode ||= currentEngine.mode
options.ignoreWhitespace ||= currentEngine.ignoreWhitespace
console.log("translating selection ", options.mode);
if (currentEngine.isDisabled == true) return alert(currentEngine.id+" is disabled!");
ui.tableCornerShowLoading();
var textPool = [];
var thisData = trans.grid.getData();
var rowPool = common.gridSelectedCells() || [];
var tempTextPool = [];
for (var i=0; i<rowPool.length; i++) {
var col = rowPool[i].col;
if (col == this.keyColumn) continue;
tempTextPool.push(thisData[rowPool[i].row][this.keyColumn]);
}
if (!tempTextPool) return;
console.log("Text pool", tempTextPool);
var translationTable = trans.generateTranslationTableFromStrings(tempTextPool, currentEngine, options);
console.log("Translation table:", JSON.stringify(translationTable, undefined, 2));
translationTable = await this.processWith("translationTableFilter", translationTable, currentEngine) || translationTable;
console.warn("Filtered translationTable", JSON.stringify(translationTable, undefined, 2));
for (var phrase in translationTable.include) {
textPool.push(phrase);
}
console.log(textPool);
console.log(rowPool);
if (rowPool.length < 1) {
ui.tableCornerHideLoading();
return;
}
var preTransData;
if (currentEngine.skipReferencePair) {
preTransData = textPool;
} else {
preTransData = trans.translateByReference(textPool);
}
console.log("Translate using : ",currentEngine.id);
// TODO: If all result has already been translated via TM, then no need to pass into Translator engine.
currentEngine.translate(preTransData, {
onAfterLoading:async function(result) {
console.log("Translation result:");
console.log(result);
console.log("text pool :");
console.log(textPool);
console.log("rowpool:");
console.log(rowPool);
console.log("translation result : ");
trans.trigger("batchTranslationResult", {original:textPool, translation:result.translation, translator:currentEngine})
var transTable = trans.generateTranslationTableFromResult(textPool, result.translation, translationTable.exclude);
console.log("translation table : ");
console.log(transTable);
trans.applyTransTableToSelectedCell(transTable, currentSelection, undefined, options);
ui.tableCornerHideLoading();
trans.grid.render();
trans.evalTranslationProgress();
trans.textEditorSetValue(trans.getTextFromLastSelected());
}
});
}
/**
* Translate selected cell with row-by-row algorighm
* @param {CellRange[]} [currentSelection=trans.grid.getSelected()] - Cell range to be translated
* @param {Object} options
* @param {Object} [options.translatorEngine=this.getActiveTranslatorEngine()] - Translator engine to be used
* @deprecated - Since 5.1.0
*/
Trans.prototype.translateSelectionByRow = async function(currentSelection, options = {}) {
options.mode = "rowByRow";
return this.translateSelection(currentSelection, options = {})
// console.log("translating selection by row");
// currentSelection = currentSelection||trans.grid.getSelected()||[[]];
// if (typeof currentSelection == 'undefined') {
// alert(t("nothing is selected"));
// return false;
// }
// if (typeof trans.translator == "undefined" || trans.translator.length < 1) {
// alert(t("no translator loaded"));
// return false;
// }
// var currentEngine = options.translatorEngine || this.getActiveTranslatorEngine();
// ui.tableCornerShowLoading();
// var textPool = [];
// var thisData = trans.grid.getData();
// var rowPool = common.gridSelectedCells() || [];
// for (var i=0; i<rowPool.length; i++) {
// var col = rowPool[i].col;
// if (col == this.keyColumn) continue;
// textPool.push(thisData[rowPool[i].row][this.keyColumn]);
// }
// textPool = this.processWith("textPoolFilter", textPool);
// console.log("Text pool", textPool)
// var dataString = textPool;
// console.log(dataString);
// console.log(rowPool);
// if (rowPool.length < 1) {
// ui.tableCornerHideLoading();
// return;
// }
// if (currentEngine.isDisabled == true) return alert(currentEngine.id+t(" is disabled!"));
// var preTransData;
// if (currentEngine.skipReferencePair) {
// preTransData = dataString;
// } else {
// preTransData = trans.translateByReference(dataString);
// }
// currentEngine.translate(preTransData, {
// mode: "rowByRow",
// onAfterLoading:function(result) {
// console.log(">Translation result:", result);
// console.log(">Row pool:", rowPool);
// if (typeof result.translation !== 'undefined') {
// for (var x in result.translation) {
// trans.data[rowPool[x].row][rowPool[x].col] = result.translation[x];
// }
// }
// ui.tableCornerHideLoading();
// trans.grid.render();
// trans.evalTranslationProgress();
// trans.textEditorSetValue(trans.getTextFromLastSelected());
// }
// });
}
/**
* Translate selected cell with line-by-line algorighm
* @param {CellRange[]} [currentSelection=trans.grid.getSelected()] - Cell range to be translated
* @param {Object} options
* @param {Object} [options.translatorEngine=this.getActiveTranslatorEngine()] - Translator engine to be used
* @deprecated - Since 5.1.0
*/
Trans.prototype.translateSelectionByLine = async function(currentSelection, options = {}) {
options.mode = "lineByLine";
return this.translateSelection(currentSelection, options = {})
// var trans = this;
// console.log("translating selection by line", options.mode);
// currentSelection = currentSelection||trans.grid.getSelected()||[[]];
// if (typeof currentSelection == 'undefined') {
// alert(t("nothing is selected"));
// return false;
// }
// if (typeof trans.translator == "undefined" || trans.translator.length < 1) {
// alert(t("no translator loaded"));
// return false;
// }
// var currentEngine = options.translatorEngine || this.getActiveTranslatorEngine();
// if (currentEngine.isDisabled == true) return alert(currentEngine.id+" is disabled!");
// ui.tableCornerShowLoading();
// var textPool = [];
// var thisData = trans.grid.getData();
// var rowPool = common.gridSelectedCells() || [];
// var tempTextPool = [];
// for (var i=0; i<rowPool.length; i++) {
// var col = rowPool[i].col;
// if (col == this.keyColumn) continue;
// tempTextPool.push(thisData[rowPool[i].row][this.keyColumn]);
// }
// if (!tempTextPool) return;
// console.log("Text pool", tempTextPool);
// var translationTable = trans.generateTranslationTableFromStrings(tempTextPool, currentEngine, options);
// console.log("Translation table:", translationTable);
// translationTable = await this.processWith("translationTableFilter", translationTable, currentEngine) || translationTable;
// console.warn("Filtered translationTable", translationTable);
// for (var phrase in translationTable.include) {
// textPool.push(phrase);
// }
// console.log(textPool);
// console.log(rowPool);
// if (rowPool.length < 1) {
// ui.tableCornerHideLoading();
// return;
// }
// //var preTransData = trans.translateByReference(textPool);
// var preTransData;
// if (currentEngine.skipReferencePair) {
// preTransData = textPool;
// } else {
// preTransData = trans.translateByReference(textPool);
// }
// console.log("Translate using : ",currentEngine.id);
// currentEngine.translate(preTransData, {
// onAfterLoading:async function(result) {
// console.log("Translation result:");
// console.log(result);
// console.log("text pool :");
// console.log(textPool);
// console.log("rowpool:");
// console.log(rowPool);
// console.log("translation result : ");
// trans.trigger("batchTranslationResult", {original:textPool, translation:result.translation, translator:currentEngine})
// var transTable = trans.generateTranslationTableFromResult(textPool, result.translation, translationTable.exclude);
// console.log("translation table : ");
// console.log(transTable);
// trans.applyTransTableToSelectedCell(transTable, currentSelection, undefined, options);
// ui.tableCornerHideLoading();
// trans.grid.render();
// trans.evalTranslationProgress();
// trans.textEditorSetValue(trans.getTextFromLastSelected());
// }
// });
}
Trans.prototype.translateSelectionAsOneLine = async function(currentSelection, options = {}) {
console.log("Merge to one line then translate");
currentSelection = currentSelection||trans.grid.getSelectedRange()||[[]];
if (typeof currentSelection == 'undefined') {
alert(t("nothing is selected"));
return false;
}
if (typeof trans.translator == "undefined" || trans.translator.length < 1) {
alert(t("no translator loaded"));
return false;
}
var currentEngine = options.translatorEngine || this.getActiveTranslatorEngine();
var originalText = this.getSelectedOriginalTexts(currentSelection);
var sourceText = this.getSelectedOriginalTextsAsOneLine(currentSelection);
var selectedRows = common.gridSelectedRows(currentSelection);
var rowIndex = {};
for (var i=0; i<selectedRows.length; i++) {
rowIndex[selectedRows[i]] = i;
}
console.log("Row index:", rowIndex);
var data = this.getData();
console.log("Original text:", originalText);
ui.tableCornerShowLoading();
var preTransData;
if (currentEngine.skipReferencePair) {
preTransData = sourceText;
} else {
preTransData = trans.translateByReference(sourceText);
}
console.log("Translating", preTransData);
currentEngine.translate(preTransData, {
mode: "rowByRow",
onAfterLoading: (result) => {
if (typeof result.translation !== 'undefined') {
console.log("result translation:", result.translation);
var formattedResult = common.cloneFormatting(originalText, result.translation.join(" "));
console.log("Formatted result:", formattedResult);
console.log("Write back the translated result according to the row");
var selectedCells = common.gridSelectedCells(currentSelection)
for (var i=0; i<selectedCells.length; i++) {
if (selectedCells[i].col == this.keyColumn) continue;
data[selectedCells[i].row][selectedCells[i].col] = formattedResult[rowIndex[selectedCells[i].row]];
}
}
ui.tableCornerHideLoading();
trans.grid.render();
trans.evalTranslationProgress();
trans.textEditorSetValue(trans.getTextFromLastSelected());
}
});
}
/**
* Translate using all availables translator into their corresponding default column.
* @param {CellRange} currentSelection
*/
Trans.prototype.translateSelectionIntoDef =function(currentSelection) {
/*
this function will translate using all available translator into their correspinding
default column.
*/
console.log("translating selection into default coloumn");
currentSelection = currentSelection||trans.grid.getSelected()||[[]];
if (typeof currentSelection == 'undefined') {
alert(t("nothing is selected"));
return false;
}
if (typeof trans.translator == "undefined" || trans.translator.length < 1) {
alert(t("no translator loaded"));
return false;
}
var textPool = [];
var thisData = trans.grid.getData();
var rowPool = [];
for (var index=0; index<currentSelection.length; index++) {
for (var row=currentSelection[index][0]; row<=currentSelection[index][2]; row++) {
rowPool.push(row);
textPool.push(thisData[row][0]);
}
}
//var dataString = textPool.join($DV.config.lineSeparator);
var dataString = textPool;
console.log(dataString);
console.log(rowPool);
for (var i=0; i<trans.translator.length; i++ ) {
console.log(i);
var currentPlugin = trans.translator[i];
var thisTranslator = this.getTranslatorEngine(currentPlugin);
if (thisTranslator.isDisabled == true) continue;
if (typeof thisTranslator.targetColumn == "undefined") {
console.warn("Skipping. Reason : TargetColumn property is not set for engine ", currentPlugin)
continue;
}
var preTransData = trans.translateByReference(dataString);
thisTranslator.translate(preTransData, {
onAfterLoading:function(result) {
if (typeof result.translation !== 'undefined') {
for (var x in result.translation) {
trans.data[rowPool[x]][thisTranslator.targetColumn] = result.translation[x];
}
}
//trans.refreshGrid();
trans.grid.render();
trans.evalTranslationProgress();
}
});
}
}
/**
* Apply translation table into selected cell
* @param {Object} transTable - Translation table formatted object
* @param {CellRange} [currentSelection=trans.grid.getSelected()] - Cell range to put translation into
* @param {String[][]} [transData=this.data] - Two dimensional array representing the grid
* @param {Object} [options]
* @param {Number} [options.indexKey=0] - Index of the key column
*/
Trans.prototype.applyTransTableToSelectedCell = function(transTable, currentSelection, transData, options) {
/*
transTable = translation table format;
selectedCell = array of cell selection
transData = either trans.data / trans.project.files['file'].data
*/
transData = transData||trans.data; // current selected file
currentSelection = currentSelection||trans.grid.getSelected()||[[]];
options = options||{};
options.indexKey = options.indexKey||0;
if (typeof currentSelection == 'undefined') {
alert(t("nothing is selected"));
return false;
}
//console.log("applyTransTableToSelectedCell", arguments);
var rowPool = common.gridSelectedCells(currentSelection) || [];
if (options.mode == "rowByRow") {
for (let i=0; i<rowPool.length; i++) {
let col = rowPool[i].col;
let row = rowPool[i].row;
if (col == this.keyColumn) continue;
if (Array.isArray(transData[row]) == false) continue;
transData[row][col] = transTable[transData[row][options.indexKey]];
}
} else {
for (let i=0; i<rowPool.length; i++) {
let col = rowPool[i].col;
let row = rowPool[i].row;
if (col == this.keyColumn) continue;
if (Array.isArray(transData[row]) == false) continue;
//console.log("---looking for", JSON.stringify(transData[row][options.indexKey]));
//console.log("transtable:", JSON.stringify(transTable, undefined, 2))
transData[row][col] = this.translateTextByLine(transData[row][options.indexKey], transTable);
}
}
}
/**
* Translate selected row with translation pane. (Live translator)
* @param {Number} row
* @param {Number} col
* @returns {Boolean}
*/
Trans.prototype.translateSelectedRow = function(row, col) {
if (typeof row == 'undefined') return false;
if (trans.config.autoTranslate == false) return false;
//if ($("#translationPane").attr("src") == "") return false;
col = col||0;
var currentText = trans.data[row][col];
if (!currentText) return false;
var translatorWindow = $("#translationPane")[0].contentWindow;
if (ui.windows['translator']) {
translatorWindow = ui.windows['translator'];
}
if (!translatorWindow.translator) return t("unable to load translator window");
translatorWindow.translator.translateAll(currentText);
return true;
}
/**
* Get translation by index of the translation portlet (Live translator)
* @param {Number} index - Index of the portlet
* @returns {String} Translated text
*/
Trans.prototype.getTranslationByIndex = function(index) {
if (typeof index == 'undefined') return false;
//if ($("#translationPane").attr("src") == "") return false;
var translatorWindow = $("#translationPane")[0].contentWindow;
if (ui.windows['translator']) {
translatorWindow = ui.windows['translator'];
}
return translatorWindow.$(".mainPane .portlet").eq(index).find(".portlet-content").text();
}
/**
* Import translation from other .trans file
* @param {String|Trans} refPath - path to the Trans File or an object content of transFile
* @param {Object} options
* @param {Number} [options.targetColumn=1] - target column to write for
* @param {Boolean} [options.overwrite=false] - Whether to overwrite the destination cell or not
* @param {String[]} [options.files] - imported selected file list
* @param {String[]} [options.destination] - destination file list
* @param {lineByLine|rowByRow|contextTrans|copyByRow} [options.compareMode=contextTrans] - Copy method
*/
Trans.prototype.importTranslation = async function(refPath, options) {
console.log("importTranslation", arguments);
if (typeof refPath == 'undefined') return trans.alert(t("Reference path cannot empty!"));
options = options || {};
options.targetColumn = options.targetColumn||1;
options.overwrite = options.overwrite||false;
options.files = options.files||[]; // imported selected file list
options.destination = options.destination||[]; // destination file list
options.compareMode = options.compareMode||0; // context to context
options.ignoreLangCheck = true; // always ignore language check!
options.destinationMode = options.destinationMode || "selected";
options.caseInsensitive ||= false;
console.log("refPath & options : ");
console.log(arguments);
//return true;
// selecting file is required
if (!trans.getSelectedId()) {
trans.selectFile($(".panel-left .fileList .data-selector").eq(0));
}
var lowerCaseText = undefined;
if (options.caseInsensitive) {
lowerCaseText = function(text) {
return text.toLowerCase().replaceAll("\r", "");
}
}
var applyImportedTranslation = async (loadedData) => {
console.log("Applying translation");
ui.loadingProgress(30, t("Collecting translation refference"));
await ui.log("Collecting translation refference");
var refTranslation = {};
var tempIndexes;
if (options.compareMode == 'lineByLine') {
refTranslation = trans.generateTranslationTableLine(loadedData.project.files, options);
let targetFiles;
if (options.destinationMode == "selected") {
targetFiles = this.getCheckedFiles()
}
tempIndexes = this.buildIndexes(targetFiles, true, {customFilter:lowerCaseText});
} else if (options.compareMode == 'rowByRow') {
refTranslation = trans.generateTranslationTable(loadedData.project.files, options);
let targetFiles;
if (options.destinationMode == "selected") {
targetFiles = this.getCheckedFiles()
}
tempIndexes = this.buildIndexes(targetFiles, false, {customFilter:lowerCaseText});
} else if (options.compareMode == 'contextTrans') {
refTranslation = trans.generateContextTranslationPair(loadedData.project.files, options);
}
//console.log("reference translation is : ", refTranslation);
//console.log(refTranslation);
loadedData = trans.mergeReference(loadedData);
var numData = Object.keys(refTranslation).length
await ui.log(`Processing ${numData} reference(s)`);
ui.loadingProgress(50, t("Applying translation!"));
var count = 0;
var lastProgress = 50;
if (options.compareMode == 'lineByLine') { // line by line
for (let key in refTranslation) {
let origKey = key;
if (options.caseInsensitive) key = lowerCaseText(key);
trans.findAndInsertWithIndexes(
key,
refTranslation[origKey],
options.targetColumn,
tempIndexes,
{
overwrite :options.overwrite,
files :options.destination,
lineByLine :true,
customFilter:lowerCaseText
});
ui.loadingProgress(50+(count/numData*50));
count++;
}
} else if (options.compareMode == 'rowByRow') { // row by row
console.log("Row by Row translation");
for (let key in refTranslation) {
let origKey = key;
if (options.caseInsensitive) key = lowerCaseText(key);
trans.findAndInsertWithIndexes(
key,
refTranslation[origKey],
options.targetColumn,
tempIndexes,
{
overwrite :options.overwrite,
files :options.destination,
insensitive :true,
customFilter:lowerCaseText
});
var thisProgress = Math.round(50+(count/numData*50))
if (thisProgress > lastProgress) {
ui.loadingProgress(50+(count/numData*50));
await ui.log(`Handled: ${count}/${numData}`);
lastProgress = thisProgress;
}
count++;
}
} else if (options.compareMode == 'contextTrans') {
console.log("Translation by context");
for (var key in refTranslation) {
trans.findAndInsertByContext(key, refTranslation[key], options.targetColumn, {
overwrite:options.overwrite,
files:options.destination
});
ui.loadingProgress(50+(count/numData*50));
count++;
}
} else if (options.compareMode == 'copyByRow') {
this.copyTranslationToRow(loadedData.project.files, options.targetColumn, options);
}
/**
* Triggered after import translation process is done
* @param {Object} options
* @param {Object} options.options
* @param {Object} options.loadedData
* @param {Object} options.refTranslation
*/
trans.trigger("onAfterImportTranslations", {
options:options,
loadedData:loadedData,
refTranslation:refTranslation
});
await common.wait(20)
trans.evalTranslationProgress();
trans.refreshGrid();
ui.loadingProgress(100, "Done!");
ui.loadingEnd();
}
ui.showLoading();
ui.loadingProgress(0, t("Importing translation"));
await common.wait(200);
if (typeof refPath == 'object' && typeof refPath.project !== 'undefined') {
// refference path already loaded
console.log("refPath is an object : ");
await applyImportedTranslation(refPath);
return true;
}
console.log("Opening "+refPath);
fs.readFile(refPath, async function (err, data) {
if (err) {
console.log("error opening file : "+refPath);
data = data.toString();
if (typeof options.onFailed =='function') options.onFailed.call(trans, data);
throw err;
} else {
ui.loadingProgress(20, t("Parsing data"));
await ui.log("Parsing data");
await common.wait(200);
data = data.toString();
//console.log(data);
var jsonData = JSON.parse(data);
console.log("Result data : ");
console.log(jsonData);
applyImportedTranslation(jsonData);
console.log("Done!");
if (typeof options.onSuccess == 'function') options.onSuccess.call(trans, jsonData);
trans.isOpeningFile = false;
}
});
}
Trans.prototype.translateAllBySelectedCells = async function(currentSelection, fileId, options) {
currentSelection = currentSelection||this.grid.getSelectedRange()||[[]];
fileId = fileId || this.getSelectedId();
options = options || {};
ui.showBusyOverlay();
await common.wait(100);
var targetFiles = this.getAllFilesExcept([fileId]);
//console.log("Target files:", targetFiles);
var selectedTransTable = this.generateSelectedTranslationTable(currentSelection, fileId, options);
console.log("Selected transtable", selectedTransTable);
var tempIndexes = this.buildIndexes(targetFiles);
var found = [];
for (var keyword in selectedTransTable) {
var foundCells = this.getFromIndexes(keyword, tempIndexes);
if (!foundCells) continue;
for (var y=0; y<foundCells.length; y++) {
var foundCell = foundCells[y];
var targetData = this.getData(foundCell.file);
for (var i=0; i<selectedTransTable[keyword].length; i++) {
var cellInfo = selectedTransTable[keyword][i];
var oldValue = targetData[foundCell.row][cellInfo.col];
if (cellInfo.col == this.keyColumn) continue;
targetData[foundCell.row][cellInfo.col] = cellInfo.value;
//console.log("Setting up file", foundCell.file, "row", foundCell.row, "col", cellInfo.col, "with value:", cellInfo.value);
found.push({
file:foundCell.file,
row:foundCell.row,
col:cellInfo.col,
oldValue:oldValue,
newValue:cellInfo.value
})
}
}
}
ui.hideBusyOverlay();
return found;
}
/**
* Get texts from selected cells on the grid
* @since 4.7.16
* @param {*} currentSelection
* @param {*} fileId
* @returns {String[]} Array of texts from the selected cells
*/
Trans.prototype.getSelectedTexts = function(currentSelection, fileId) {
currentSelection = currentSelection||this.grid.getSelectedRange()||[[]];
fileId = fileId || this.getSelectedId();
var data = this.getData(fileId);
var selectedCells = common.gridSelectedCells(currentSelection);
var result = [];
for (var i=0; i<selectedCells.length; i++) {
result.push(
data[selectedCells[i].row][selectedCells[i].col] || ""
)
}
return result;
}
/**
* Get text from the grid
* @param {Number} row - Row index
* @param {Number} column - Column index
* @param {String} [file] - File id
* @returns {String} - Text of the selected row, column and file
*/
Trans.prototype.getText = function(row, column, file) {
if (!file) {
return this.data?.[row]?.[column]
}
const obj = trans.getObjectById(file);
return obj?.[row]?.[column]
}
/**
* Get cell comments on a coordinate
* @param {Number} row - Row index
* @param {Number} column - Column index
* @param {String} [file] - File id
* @returns {String} - Comment of the selected row, column and file
*/
Trans.prototype.getCellComment = function(row, column, file) {
let obj
if (!file) {
obj = this.getSelectedObject();
} else {
obj = this.getObjectById(file)
}
if (!obj.comments) return;
return obj.comments?.[row]?.[column];
}
/**
* Select original texts from grid selection
* @param {CellRange[]|Number[][]} currentSelection - Current selection
* @param {String} fileId - File id
* @returns {String[]} Array of string of the selected original texts
*/
Trans.prototype.getSelectedOriginalTexts = function(currentSelection, fileId) {
currentSelection = currentSelection||this.grid.getSelectedRange()||[[]];
fileId = fileId || this.getSelectedId();
var data = this.getData(fileId);
var selectedCells = common.gridSelectedCells(currentSelection);
var result = [];
for (var i=0; i<selectedCells.length; i++) {
result.push(
data[selectedCells[i].row][this.keyColumn] || ""
)
}
return result;
}
/**
* Get texts from selected cells and merge them into one line
* @since 4.7.16
* @param {*} currentSelection
* @param {*} fileId
* @returns {String} Text of the selected cell in one line
*/
Trans.prototype.getSelectedTextsAsOneLine = function(currentSelection, fileId) {
var texts = this.getSelectedTexts(currentSelection, fileId) || [];
var merged = texts.join(" ");
return merged.replaceAll("\r", "").replaceAll("\n", " ");
}
/**
* Get original texts from selected cells and merge them into one line
* @since 4.7.16
* @param {*} currentSelection
* @param {*} fileId
* @returns {String} Original text of the selected cell in one line
*/
Trans.prototype.getSelectedOriginalTextsAsOneLine = function(currentSelection, fileId) {
currentSelection = currentSelection||this.grid.getSelectedRange()||[[]];
fileId = fileId || this.getSelectedId();
var data = this.getData(fileId);
var rows = common.gridSelectedRows()
var result = []
for (var i=0; i<rows.length; i++) {
result.push(
data[rows[i]][this.keyColumn] || ""
)
}
return result.join(" ");
}
// ==================================================================
// PUT DOM RELATED CODE HERE
// ==================================================================
/**
* returns related key ID from trans.project.files
* returning false when error
* @returns {String|False} The currently seleceted ID
*/
Trans.prototype.getSelectedId = function() {
// returning false when error
// returns related key ID from trans.project.files
// getting the value from trans.project.selectedId is faster than DOM
//if (trans.project?.selectedId) return trans.project.selectedId;
if (!trans.project) return false;
return trans.project?.selectedId
/*
if ($(".fileList .selected").length == 0 ) return false;
return $(".fileList .selected").data("id");
*/
}
/**
* Get selected row's context
* @param {Number} rowNumber - Row id to look for
* @returns {String} Context
*/
Trans.prototype.getSelectedContext = function(rowNumber) {
rowNumber = rowNumber || trans.lastSelectedCell[0]
var context = trans.getSelectedObject().context;
try {
return context[rowNumber]
} catch (e) {
context[rowNumber] = [];
return context[rowNumber];
}
}
/**
* Get selected row's parameters
* @returns {Object} parameters of the selected row
*/
Trans.prototype.getSelectedParameters = function() {
if (!trans.lastSelectedCell) return;
var rowNumber = trans.lastSelectedCell[0]
var obj =trans.getSelectedObject()
obj.parameters = obj.parameters || [];
obj.parameters[rowNumber] = obj.parameters[rowNumber] || []
return obj.parameters[rowNumber]
}
/**
* Get parameters by row id & file id
* @param {Number} row - The row id
* @param {String} file - The file id
* @returns {Object|false} parameters of the selected row or false if no parameter is found
*/
Trans.prototype.getParamatersByRow = function(row, file) {
file = file || this.getSelectedId();
if (typeof row !== "number") return false;
var thisObj = trans.getObjectById(file);
if (!thisObj?.parameters) return false;
return thisObj.parameters[row];
}
/**
* Get row info text from file object's parameters
* @param {Number} row - Row index
* @param {Boolean} [full=false] - Whether to display full result or not
* @param {String} [file=Trans.getSelectedId()] - Target file object
* @returns {String} row info
*/
Trans.prototype.getRowInfoText = function(row, full=false, file="") {
file = file || this.getSelectedId();
const thisParam = this.getParamatersByRow(row, file);
if (!thisParam) return "";
const rowInfoReference = (this.getOption("gridInfo")||{}).referenceName || "Actor Reference";
var result;
if (full) {
result = [];
for (let i in thisParam) {
if (!thisParam[i]) continue;
if (typeof thisParam[i] !== "object") continue;
if (thisParam[i].rowInfoText) result.push(this.translateByReference(thisParam[i].rowInfoText, false, rowInfoReference));
}
let uniqueItems = [...new Set(result)]
return uniqueItems.join(", ")
}
result = "";
for (let i in thisParam) {
if (!thisParam[i]) continue;
if (typeof thisParam[i] !== "object") continue;
if (result) return result+"++";
if (thisParam[i].rowInfoText) result = this.translateByReference(thisParam[i].rowInfoText, false, rowInfoReference);
}
return result;
}
/**
* Get selected key text from row
* Usually cell 0
* @param {*} rowNumber - Row id
* @returns {String|undefined} key text
*/
Trans.prototype.getSelectedKeyText = function(rowNumber) {
rowNumber = rowNumber || trans.lastSelectedCell[0]
try {
var data = trans.getSelectedObject().data;
return data[rowNumber][trans.keyColumn]
} catch (e) {
// do nothing
}
}
/**
* returns related object from trans.project.files[currently selected]
* returning false when error
* @returns {Object|false}
*/
Trans.prototype.getSelectedObject = function() {
// returning false when error
// returns related object from trans.project.files[currently selected]
if ($(".fileList .selected").length == 0 ) return false;
var currentID = trans.getSelectedId();
return trans.project.files[currentID];
}
/**
* Get file object by its id
* @param {String} id - The file id
* @returns {Object} the file object. Equal to trans.project.files[id]
*/
Trans.prototype.getObjectById = function(id) {
if (!id) return;
try {
return trans.project.files[id]
} catch (e){
console.warn(e);
return;
}
}
/**
* Get list of the checked file(s) on the left pane
* @returns {String[]} Array of the checked file id
*/
Trans.prototype.getCheckedFiles = function() {
var result = [];
var checkbox = $(".fileList .data-selector .fileCheckbox:checked");
for (var i=0; i<checkbox.length; i++) {
result.push(checkbox.eq(i).attr("value"));
}
return result;
}
/**
* Get selected objects
* @returns {Object} Selected object
*/
Trans.prototype.getCheckedObjects = function() {
var result = {};
var checkbox = $(".fileList .data-selector .fileCheckbox:checked");
for (var i=0; i<checkbox.length; i++) {
var id = checkbox.eq(i).attr("value");
result[id] = this.getObjectById(id)
}
return result;
}
/**
* Get all file ids on the project
* @param {Object} [obj=this.project.files] - list of file objects
* @param {Boolean} [excludeReference=false] - wether to exclude reference or not
* @returns {String[]}
*/
Trans.prototype.getAllFiles = function(obj, excludeReference) {
var result = [];
obj = obj||trans?.project?.files;
if (typeof obj == 'undefined') return result;
if (obj?.project?.files) {
obj = obj?.project?.files;
}
if (excludeReference) {
for (let file in obj ) {
if (obj[file]?.dirname == "*") continue; // skip reference
result.push(file);
}
return result;
}
for (let file in obj ) {
result.push(file);
}
return result;
}
/**
* Get all file ids on the project, except filteredIds
* @since 4.7.17
* @param {String[]} filteredIds - list of exceptions
* @param {Object} [obj=this.project.files] - list of file objects
* @returns {String[]}
*/
Trans.prototype.getAllFilesExcept = function(filteredIds, obj) {
if (typeof filteredIds == "string") filteredIds = [filteredIds];
filteredIds = filteredIds || [];
obj = obj||trans.project.files;
var result = [];
for (var file in obj ) {
if (filteredIds.includes(file)) continue;
result.push(file);
}
return result;
}
/**
* Get all files with 100% progress
* @param {*} [obj=this.project.files] - list of file objects
* @returns {String[]} List of the files
*/
Trans.prototype.getAllCompletedFiles = function(obj) {
var result = [];
obj = obj||this.project.files;
if (typeof obj == 'undefined') return result;
for (var file in obj ) {
//console.log(file, obj[file]);
if (typeof obj[file] !== "object") continue;
if (typeof obj[file].progress !== "object") continue;
if (obj[file].progress.percent == 100) {
result.push(file);
}
}
return result;
}
/**
* Get all files with less than 100% progress
* @param {*} [obj=this.project.files] - list of file objects
* @returns {String[]} List of the files
*/
Trans.prototype.getAllIncompletedFiles = function(obj) {
var result = [];
obj = obj||this.project.files;
if (typeof obj == 'undefined') return result;
for (var file in obj ) {
//console.log(file, obj[file]);
if (typeof obj[file] !== "object") continue;
if (typeof obj[file].progress !== "object") continue;
if (obj[file].progress.percent < 100) {
result.push(file);
}
}
return result;
}
/**
* Get all files marked as completed
* @param {*} [obj=this.project.files] - list of file objects
* @returns {String[]} List of the files
* @since 4.4.5
*/
Trans.prototype.getAllMarkedAsCompleted = function(obj) {
var result = [];
obj = obj||this.project.files;
if (typeof obj == 'undefined') return result;
for (var file in obj ) {
if (typeof obj[file] !== "object") continue;
if (typeof obj[file].progress !== "object") continue;
if (obj[file].isCompleted) {
result.push(file);
}
}
return result;
}
/**
* Get attachment content by ID
* @param {String} id - ID of the attachment
* @since 6.3.27
* @returns {undefined|String} - Attachment content
*/
Trans.prototype.getAttachmentContent = function(id) {
if (!id) return;
if (!trans.project?.attachments?.[id]) return;
return trans.project.attachments[id].data;
}
/**
* Scroll horizontally to the selected column.
* The column in argument 1 will be displayed next to the frozen cols
* @param {*} col - Column index to be shown
*/
Trans.prototype.scrollHToCol = function(col) {
var $container = $("#table .ht_master .wtHolder")
if (!col) return $container[0].scrollLeft = 0
var $colls = $("#table .ht_master .wtHolder colgroup col");
//var margin = $colls.eq(0).outerWidth()
var scrollWidth = 0
for (var i=1; i<col; i++) {
scrollWidth += $colls.eq(i+1).outerWidth();
}
console.log(scrollWidth)
$container[0].scrollLeft = scrollWidth;
}
// ==============================================================
// SEARCH
// ==============================================================
/**
* Go to cell. Will select the cell and scroll the viewport to the cell.
* @param {Number} row
* @param {Number} col
* @param {String|JQuery} fileId
*/
Trans.prototype.goTo = function(row, col, fileId) {
console.log(arguments);
fileId = fileId || this.getSelectedId()
/*
trans.selectFile(context, {
onDone:function() {
trans.grid.selectCell(row,col,row,col);
}
});
*/
// commit any change on current cell
this.grid.deselectCell();
if (fileId !== this.getSelectedId()) {
var $selected = this.selectFile(fileId);
//$($selected)[0].scrollIntoView({behavior: "smooth"});
$($selected)[0].scrollIntoView({block:"center"});
}
this.grid.selectCell(row,col,row,col);
this.grid.scrollViewportTo(row,col);
this.scrollHToCol(col);
//setTimeout (function() {trans.grid.selectCell(row,col,row,col)}, 1000);
}
/**
* Get the last selected Cell
* @returns {Number[]} Array of row and column
* Return `[0,0]` if no cell is selected;
* @since 4.4.4
*/
Trans.prototype.getLastSelectedCell = function() {
return this.lastSelectedCell;
}
/**
* Go to the next untranslated cell
* @returns {Boolean} Whether the operation is successful or not
*/
Trans.prototype.goToNextUntranslated = function() {
var selectedCell = this.getLastSelectedCell();
var data = this.getCurrentData();
var starting = selectedCell[0]+1;
if (starting >= data.length) starting = 0;
for (var rowId=starting; rowId<data.length; rowId++) {
if (this.rowHasTranslation(data[rowId])) continue;
break;
}
if (rowId >= data.length) rowId = data.length - 1;
return this.goTo(rowId, selectedCell[1]);
}
/**
* Go to the previous untranslated cell
* @returns {Boolean} Whether the operation is successful or not
*/
Trans.prototype.goToPreviousUntranslated = function() {
var selectedCell = this.getLastSelectedCell();
var data = this.getCurrentData();
var starting = selectedCell[0]-1;
if (starting <= 0) starting = data.length - 1;
console.log("starting", starting);
for (var rowId=starting; rowId>=0; rowId--) {
if (this.rowHasTranslation(data[rowId])) continue;
break;
}
console.log("Move to row", rowId);
if (rowId < 0) rowId = 0;
return this.goTo(rowId, selectedCell[1]);
}
/**
* Search for some keyword
* @param {String} keyword - Keyword
* @param {Object} options
* @param {Boolean} [options.caseSensitive] - Whether the search is in case sensitive mode or not
* @param {Boolean} [options.lineMatch] - Whether the search is with the line match mode or not
* @param {Boolean} [options.isRegexp] - Whether the keyword is a regexp or not
* @returns {SearchResult} the search result
*/
Trans.prototype.search = function(keyword, options) {
var globToRegExp = require('glob-to-regexp');
console.log("entering trans.search", arguments);
if (typeof keyword == "undefined") return null;
if (typeof keyword.length <=1) return "Keyword too short!";
if (typeof trans.project == "undefined") return null;
if (typeof trans.project.files == "undefined") return null;
options = options|| {};
options.caseSensitive = options.caseSensitive||false;
options.lineMatch = options.lineMatch||false;
options.isRegexp = options.isRegexp||false;
options.searchLocations = options.searchLocations || [];
if (options.searchLocations.length == 0) options.searchLocations = ['grid'];
if (options.lineMatch) options.searchInContext = false;
if (Array.isArray(options.files) == false) {
options.files = [];
for (let file in trans.project.files) {
options.files.push(file);
}
}
if (options.caseSensitive == false && options.isRegexp == false) {
keyword = keyword.toLowerCase();
}
var start = new Date().getTime();
/**
* @typedef SearchResult
* @property {String} keyword - The keyword used
* @property {Number} count - Total numbers of the result
* @property {Boolean} isRegExp - Whether the keyword is regular expression or not
* @property {Number} executionTime - Execution time in ms
* @property {Object} files - list of the search result by file id
* @property {String} files.fullString - Full string of the result
* @property {Number} files.row - Row id
* @property {Number} files.col - Column id
* @property {cell|context|comment} files.type - type of the search result
* @property {Number} files.lineIndex - The line index
*/
var result = {
keyword:keyword,
count:0,
isRegexp:options.isRegexp,
executionTime:0,
files:{}
};
// check if regexp is valid
var keywordExp;
if (options.isRegexp) {
keywordExp = common.evalRegExpStr(keyword);
if (keywordExp == false) {
alert(keyword+t(" is not a valid javascript's regexp!\r\nFind out more about Javascipt's Regular Expression at :\r\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions"));
return result;
}
} else if (keyword.includes("*")) {
// glob style keyword
try {
var fixedKeywordExp = globToRegExp(keyword).toString().replace(/\/\^(.*?)\$\//, '$1');
keywordExp = new RegExp(fixedKeywordExp, 'i'); // insensitive
console.log("Glob style keyword detected", keywordExp);
} catch (e) {
console.warn('Error when converting glob pattern to RegExp');
}
}
//line match algorithm
if (options.lineMatch) {
for (let cont in options.files) {
let file = options.files[cont];
if (Array.isArray(trans.project.files[file].data) == false) continue;
let currentFile = trans.project.files[file].data;
for (let row=0; row<currentFile.length; row++) {
if (currentFile[row].length == 0) continue;
for (let col=0; col<currentFile[row].length; col++) {
if (typeof currentFile[row][col] !== "string") continue;
if (keywordExp) {
// regular expression search
if (keywordExp.test(currentFile[row][col])) {
// match found
let lineIndex = common.lineIndexRegExp(currentFile[row][col], keywordExp);
result.files[file] = result.files[file]||[];
result.files[file].push({
'fullString':currentFile[row][col],
'row':row,
'col':col,
'type':'cell',
'lineIndex':lineIndex
});
result.count++;
break;
}
} else {
// normal search
if (options.caseSensitive) {
if (currentFile[row][col].indexOf(keyword) == -1) continue;
} else {
if (currentFile[row][col].toLowerCase().indexOf(keyword) == -1) continue;
}
let lineIndex = common.lineIndex(currentFile[row][col], keyword, options.caseSensitive);
if (lineIndex != -1) {
// match found
result.files[file] = result.files[file]||[];
result.files[file].push({
'fullString':currentFile[row][col],
'row':row,
'col':col,
'type':'cell',
'lineIndex':lineIndex
});
result.count++;
break;
}
}
}
}
}
let end = new Date().getTime();
result.executionTime = end - start;
return result;
}
console.log("Search location:", options.searchLocations);
// common algorithm
if (options.searchLocations.includes("grid")) {
//for (var file in trans.project.files) {
for (let cont in options.files) {
let file = options.files[cont];
if (Array.isArray(trans.project.files[file].data) == false) continue;
let currentFile = trans.project.files[file].data;
for (let row=0; row<currentFile.length; row++) {
if (currentFile[row].length == 0) continue;
for (let col=0; col<currentFile[row].length; col++) {
if (typeof currentFile[row][col] !== "string") continue;
if (keywordExp) {
//console.log("regexp search:", keywordExp, currentFile[row][col], keywordExp.test(currentFile[row][col]));
//console.log(typeof keywordExp);
// regular expression search
if (keywordExp.test(currentFile[row][col])) {
// match found
result.files[file] = result.files[file]||[];
result.files[file].push({
'fullString':currentFile[row][col],
'row':row,
'col':col,
'type':'cell'
});
result.count++;
break;
}
} else {
if (options.caseSensitive) {
if (currentFile[row][col].indexOf(keyword) == -1) continue;
} else {
if (currentFile[row][col].toLowerCase().indexOf(keyword) == -1) continue;
}
result.files[file] = result.files[file]||[];
result.files[file].push({
'fullString':currentFile[row][col],
'row':row,
'col':col,
'type':'cell'
});
result.count++;
}
}
}
}
}
if (options.searchLocations.includes("context")) {
for (let idx in options.files) {
let file = options.files[idx];
if (Array.isArray(trans.project.files[file].context) == false) continue;
let currentFile = trans.project.files[file].context;
for (let row=0; row<currentFile.length; row++) {
if (Array.isArray(currentFile[row]) == false) continue;
if (currentFile[row].length == 0) continue;
for (let cont=0; cont<currentFile[row].length; cont++) {
if (typeof currentFile[row][cont] !== "string") continue;
if (keywordExp) {
if (keywordExp.test(currentFile[row][cont]) == false) continue;
} else if (options.caseSensitive) {
if (currentFile[row][cont].indexOf(keyword) == -1) continue;
} else {
if (currentFile[row][cont].toLowerCase().indexOf(keyword) == -1) continue;
}
result.files[file] = result.files[file]||[];
result.files[file].push({
'fullString':currentFile[row][cont],
'row':row,
'col':0,
'type':'context'
});
result.count++;
}
}
}
}
if (options.searchLocations.includes("tag")) {
for (let cont in options.files) {
let file = options.files[cont];
let tags = keyword.split(" ");
let currentFile = trans.project.files[file].data;
for (let row=0; row<currentFile.length; row++) {
if (trans.hasTags(tags, row, file) == false) continue;
result.files[file] = result.files[file]||[];
result.files[file].push({
'fullString':currentFile[row][this.keyColumn],
'row':row,
'col':0,
'type':'cell'
});
result.count++;
}
}
}
if (options.searchLocations.includes("comment")) {
console.log("searching comment");
for (let idx in options.files) {
let file = options.files[idx];
if (Boolean(trans.project.files[file].comments) == false) continue;
let currentFile = trans.project.files[file].comments;
console.log("processing", file);
for (let row in currentFile) {
if (Boolean(currentFile[row]) == false) continue;
for (let col in currentFile[row]) {
if (typeof currentFile[row][col] !== "string") continue;
if (keywordExp) {
if (keywordExp.test(currentFile[row][col]) == false) continue;
} else if (options.caseSensitive) {
if (currentFile[row][col].indexOf(keyword) == -1) continue;
} else {
if (currentFile[row][col].toLowerCase().indexOf(keyword) == -1) continue;
}
result.files[file] = result.files[file]||[];
result.files[file].push({
'fullString':currentFile[row][col],
'row':row,
'col':col,
'type':'comment'
});
result.count++;
}
}
}
}
var end = new Date().getTime();
result.executionTime = end - start;
return result;
}
/**
* Search for some keyword and replace it with a text
* @param {String} keyword - Keyword
* @param {String} replacer - Replacer
* @param {Object} options
* @param {Boolean} [options.caseSensitive] - Whether the search is in case sensitive mode or not
* @param {Boolean} [options.lineMatch] - Whether the search is with the line match mode or not
* @param {Boolean} [options.isRegexp] - Whether the keyword is a regexp or not
* @returns {SearchResult} the search result
*/
Trans.prototype.replace = function(keyword, replacer, options) {
console.log("entering trans.search");
if (typeof keyword == "undefined") return null;
if (typeof keyword.length <=1) return t("Keyword too short!");
if (typeof trans.project == "undefined") return null;
if (typeof trans.project.files == "undefined") return null;
replacer = replacer||"";
options = options|| {};
options.caseSensitive = options.caseSensitive||false;
options.isRegexp = options.isRegexp||false;
if (Array.isArray(options.files) == false) {
options.files = [];
for (var file in trans.project.files) {
options.files.push(file);
}
}
if (options.caseSensitive == false && options.isRegexp == false) {
keyword = keyword.toLowerCase();
}
var start = new Date().getTime();
var result = {
'keyword':keyword,
count:0,
isRegexp:options.isRegexp,
executionTime:0,
files:{}
};
// check if regexp is valid
if (options.isRegexp) {
var keywordExp = common.evalRegExpStr(keyword);
if (keywordExp == false) {
alert(keyword+t(" is not a valid javascript's regexp!\r\nFind out more about Javascipt's Regular Expression at :\r\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions"));
return result;
}
}
//for (var file in trans.project.files) {
for (let cont in options.files) {
let file = options.files[cont];
if (Array.isArray(trans.project.files[file].data) == false) continue;
let currentFile = trans.project.files[file].data;
//console.log("handling file : ", file, currentFile);
for (let row=0; row<currentFile.length; row++) {
if (currentFile[row].length == 0) continue;
for (let col=1; col<currentFile[row].length; col++) { // skip first row
if (typeof currentFile[row][col] !== "string") continue;
if (options.isRegexp) {
// regular expression search
if (keywordExp.test(currentFile[row][col])) {
// match found
let original = trans.project.files[file].data[row][col];
trans.project.files[file].data[row][col] = currentFile[row][col].replace(keywordExp, replacer);
result.files[file] = result.files[file]||[];
result.files[file].push({
'fullString':trans.project.files[file].data[row][col],
'row':row,
'col':col,
'originalString' : original
});
result.count++;
break;
}
continue;
}
// normal search
if (options.caseSensitive) {
if (currentFile[row][col].indexOf(keyword) == -1) continue;
} else {
if (currentFile[row][col].toLowerCase().indexOf(keyword) == -1) continue;
}
let original = trans.project.files[file].data[row][col];
trans.project.files[file].data[row][col] = currentFile[row][col].replaces(keyword, replacer, !options.caseSensitive);
result.files[file] = result.files[file]||[];
result.files[file].push({
'fullString':trans.project.files[file].data[row][col],
'row':row,
'col':col,
'originalString' : original
});
result.count++;
}
}
}
var end = new Date().getTime();
result.executionTime = end - start;
trans.refreshGrid();
//trans.selectFile($(".fileList .selected"));
return result;
}
/**
* Search for some keyword and put the text on target column
* @param {String} keyword - Keyword
* @param {String} put - The text to put into
* @param {Number} targetCOl - The index of the column to put into
* @param {Object} options
* @param {Boolean} [options.caseSensitive] - Whether the search is in case sensitive mode or not
* @param {Boolean} [options.lineMatch] - Whether the search is with the line match mode or not
* @param {Boolean} [options.isRegexp] - Whether the keyword is a regexp or not
* @returns {SearchResult} the search result
*/
Trans.prototype.findPut = function(keyword, put, targetCol, options) {
console.log("entering trans.search");
if (typeof keyword == "undefined") return null;
if (typeof keyword.length <=1) return t("Keyword too short!");
if (typeof trans.project == "undefined") return null;
if (typeof trans.project.files == "undefined") return null;
if (targetCol < 1) return false;
options = options|| {};
options.caseSensitive = options.caseSensitive||false;
options.lineMatch = options.lineMatch||false;
if (typeof options.overwrite == "undefined") options.overwrite = true;
if (Array.isArray(options.files) == false) {
options.files = [];
for (var file in trans.project.files) {
options.files.push(file);
}
}
if (options.caseSensitive == false) {
keyword = keyword.toLowerCase();
}
var start = new Date().getTime();
var result = {
keyword :keyword,
count :0,
executionTime:0,
files:{}
};
// todo: If keyword contains more than one line, then use row by row matching
if (keyword.includes("\n") || options.mode=="rowByRow") {
console.log("Entering row by row search");
result = this.findAndInsert(keyword, put, targetCol , {
insensitive : !options.caseSensitive,
overwrite : options.overwrite
});
} else {
//line match algorithm
for (let cont in options.files) {
let file = options.files[cont];
if (Array.isArray(trans.project.files[file].data) == false) continue;
let currentFile = trans.project.files[file].data;
for (let row=0; row<currentFile.length; row++) {
if (currentFile[row].length == 0) continue;
for (let col=0; col<currentFile[row].length; col++) {
if (typeof currentFile[row][col] !== "string") continue;
if (options.caseSensitive) {
if (currentFile[row][col].indexOf(keyword) == -1) continue;
} else {
if (currentFile[row][col].toLowerCase().indexOf(keyword) == -1) continue;
}
var lineIndex = common.lineIndex(currentFile[row][col], keyword, options.caseSensitive);
if (lineIndex != -1) {
// match found
var newTxt = common.insertLineAt(currentFile[row][targetCol], put, lineIndex, {
lineBreak:trans.project.files[file].lineBreak||"\n"
});
currentFile[row][targetCol] = newTxt;
result.files[file] = result.files[file]||[];
result.files[file].push({
'fullString':currentFile[row][col],
'row':row,
'col':col,
'type':'cell',
'lineIndex':lineIndex
});
result.count++;
break;
}
}
}
}
}
var end = new Date().getTime();
result.executionTime = end - start;
trans.refreshGrid();
return result;
}
/**
* Hooks
*/
class TransProjectHook{
constructor () {
this.hooks = {}
}
}
TransProjectHook.prototype.defineHook = function(hookName, fn) {
if (typeof hookName !== "string") throw new Error(`hookName must be a string ${typeof hookName} given`)
if (typeof fn !== "function") throw new Error(`hookName must be a function ${typeof fn} given`)
this.hooks[hookName] = fn;
}
TransProjectHook.prototype.getHook = function(hookName) {
return this.hooks[hookName];
}
TransProjectHook.prototype.run = async function(hookName, ...args) {
if (typeof this.hooks[hookName] == "function") {
await this.hooks[hookName].apply(window.trans, args);
}
// if not defined then look for the setting in trans.project.options.hooks
const hooks = trans.getOption("hooks");
if (!hooks) return;
if (typeof hooks?.afterExport !== "string") return;
const userConsent = await ui.confirmRunScript();
if (!userConsent) return;
let AsyncFunction = Object.getPrototypeOf(async function(){}).constructor
const newFunc = new AsyncFunction(hooks.afterExport);
const execResult = newFunc.apply(trans, args);
return execResult;
}
//================================================================
// NEW TRANS INSTANCE
//================================================================
/**
* @namespace
* @instance
*/
var trans = new Trans()
window.trans = trans;
trans.cellInfo = new Trans.CellInfo();
trans.projectHook = new TransProjectHook();
//trans.projectHook.hooks = require("www/js/trans.projecthooks.js");
/**
* Grid context menu object
* @memberof! Trans#
*/
trans.gridContextMenu = {
'commentsAddEdit': {
name: t("Add comment"),
hidden: function() {
if (trans.grid.isColumnHeaderSelected()) return true;
if (trans.grid.isRowHeaderSelected()) return true;
return false;
}
},
'commentsRemove':{
name: t("Delete comment"),
hidden: function() {
if (trans.grid.isColumnHeaderSelected()) return true;
if (trans.grid.isRowHeaderSelected()) return true;
return false;
}
},
"sepx": '---------',
'translateThisCell':{
name: function() {
var def = "<span class='HOTMenuTranslateHere'>"+t("Translate here")+" <kbd>ctrl+g</kbd></span>";
if (typeof trans.project == 'undefined') return def;
trans.project.options = trans.project.options||{};
var thisTrans = trans.getActiveTranslator()
console.log("thisTrans", thisTrans);
if (typeof thisTrans == 'undefined') return def;
var from = trans.getSl()||"??";
var to = trans.getTl()||"??";
var thisTranslatorName = trans.getTranslatorEngine(thisTrans)?.name;
if (!thisTranslatorName) return def;
return t("Translate here using ")+thisTranslatorName+" ("+from+"<i class='icon-right-bold'></i>"+to+") <kbd>ctrl+g</kbd>"
},
callback: function() {
trans.translateSelection();
},
hidden: function() {
if (trans.grid.isColumnHeaderSelected()) return true;
if (trans.grid.isRowHeaderSelected()) return true;
if (!trans.getActiveTranslator()) return true;
return false;
}
},
'translateUsing': {
name: function() {
trans.drawGridTranslatorMenu();
return t('Translate using...')
},
submenu: {
items: []
},
hidden: function() {
if (trans.grid.isRowHeaderSelected()) return true;
return false;
}
},
'mergeThenTranslate': {
name: "<span>Merge then translate <kbd>ctrl+shift+g</kbd></span>",
hidden: function() {
if (trans.grid.isRowHeaderSelected()) return true;
return false;
},
callback: async function() {
trans.translateSelectionAsOneLine();
}
},
'translateSimiliar': {
name: "<span>Translate like this <kbd>ctrl+l</kbd></span>",
hidden: function() {
if (trans.grid.isRowHeaderSelected()) return true;
return false;
},
callback: async function() {
var conf = confirm(t("Do you want to translate all the same texts found across this project with the translation on the selected cell(s)?"))
if (!conf) return;
var result = await trans.translateAllBySelectedCells();
alert(result.length + t(` cell(s) has been written!`))
}
},
'---------':{},
'columnWidth': {
name: t("Column width"),
callback: function(origin, selection, e) {
console.log("column width : ", arguments);
var cols = common.gridSelectedCols();
var width = prompt("Enter new width", this.getColWidth(cols[0]));
width = parseInt(width);
if (width < 1) return alert(t("Width must greater than 0"))
for (var i=0; i<cols.length; i++) {
this.setColWidth(cols[i], width)
}
},
hidden: function() {
if (this.isColumnHeaderSelected()) return false;
if (this.isRowHeaderSelected()) return true;
return true;
}
},
'sepn' :{
name: '---------',
hidden: function() {
if (this.isColumnHeaderSelected()) return false;
if (this.isRowHeaderSelected()) return true;
return true;
}
},
'col-right': {
name: t("Insert column right"),
callback: function() {
trans.grid.insertColumnRight();
},
hidden: function() {
if (trans.grid.isColumnHeaderSelected()) return false;
if (trans.grid.isRowHeaderSelected()) return true;
return true;
}
//disabled: false
},
'duplicateCol': {
name: t("Duplicate column"),
callback: function() {
var getCol = trans.grid.getSelected()[0][1];
var getColSet = trans.columns.length;
var colHeaderName = trans.colHeaders[getCol]||"New Col";
//var currentData = trans.grid.getData();
trans.columns.push({});
common.arrayExchange(trans.columns, getColSet, getCol + 1);
common.arrayInsert(trans.colHeaders, getCol+1, colHeaderName);
//batchArrayInsert(trans.data, getCol+1, null);
console.log(trans.columns);
trans.insertCell(getCol+1, null);
trans.copyCol(getCol, getCol+1);
trans.grid.updateSettings({
colHeaders:trans.colHeaders
})
//trans.grid.render()
},
hidden: function() {
if (trans.grid.isColumnHeaderSelected()) {
if (trans.grid.getSelected()[0][1] != trans.grid.getSelected()[0][3] ||
trans.grid.getSelected().length > 1 ) return true;
return false;
}
return true;
}
//disabled: false
},
'removeColumn': {
name: t("Remove this column"),
callback: function(origin, selection, e) {
console.log(arguments);
var conf = confirm(t("Remove selected column?\nThis can not be undone!"));
if (conf) {
trans.removeColumn(selection[0].start.col, {refreshGrid:true});
}
},
hidden: function() {
if (trans.grid.isColumnHeaderSelected()) return false;
if (trans.grid.isRowHeaderSelected()) return true;
return true;
}
},
'renameColumn': {
name: t("Rename this column"),
callback: function(origin, selection, e) {
console.log(arguments);
var thisCol = selection[0].start.col;
var colName = trans.colHeaders[thisCol];
var conf = prompt(t("Please enter new name"), colName);
if (conf) {
trans.renameColumn(thisCol, conf, {refreshGrid:true});
}
},
hidden: function() {
if (trans.grid.isColumnHeaderSelected()) return false;
if (trans.grid.isRowHeaderSelected()) return true;
return true;
}
},
"sep0": '---------',
'tags': {
name:"Tags",
submenu: {
items: [
{
key: 'tags:red',
name: '<i class="tag red icon-circle"></i> '+t('Red'),
callback: function(key, selection, clickEvent) {
trans.setTagForSelectedRow("red", selection);
}
},
{
key: 'tags:yellow',
name: '<i class="tag yellow icon-circle"></i> '+t('Yellow'),
callback: function(key, selection, clickEvent) {
trans.setTagForSelectedRow("yellow", selection);
}
},
{
key: 'tags:green',
name: '<i class="tag green icon-circle"></i> '+t('Green'),
callback: function(key, selection, clickEvent) {
trans.setTagForSelectedRow("green", selection);
}
},
{
key: 'tags:blue',
name: '<i class="tag blue icon-circle"></i> '+t('Blue'),
callback: function(key, selection, clickEvent) {
trans.setTagForSelectedRow("blue", selection);
}
},
{
key: 'tags:gold',
name: '<i class="tag gold icon-circle"></i> '+t('Gold'),
callback: function(key, selection, clickEvent) {
trans.setTagForSelectedRow("gold", selection);
}
},
{
key: 'tags:more',
name: '<i class="tag icon-tags"></i> '+t('More tags...')+' <kbd>ctrl+t</kbd>',
callback: function(key, selection, clickEvent) {
//setTimeout(function() {
ui.taggingDialog(selection);
//}, 0);
}
},
{
key: 'tags:clear',
name: '<i class="tag icon-blank"></i> '+t('Clear tags'),
callback: function(key, selection, clickEvent) {
trans.clearTags(undefined, selection);
trans.grid.render();
}
}
]
}
},
"sep1": '---------',
'deleteRow': {
name: function() {
return t("Delete Row")+" <kbd>shift+del</kbd>"
},
callback: function(origin, selection, e) {
var conf = confirm(t("Do you want to remove the currently selected row(s)?"));
if (!conf) return;
trans.removeRow(trans.getSelectedId(), common.gridSelectedRows());
trans.refreshGrid();
trans.grid.deselectCell();
},
hidden: function() {
if (trans.grid.isColumnHeaderSelected()) return true;
if (trans.grid.isRowHeaderSelected()) return false;
return false;
}
},
'clearContextTranslation': {
name: t("Clear Context Translation")+" <kbd>alt+del</kbd>",
callback: function(origin, selection, e) {
/**
* Trigger when user runs Clear Context Translation
* @event Trans#clearContextTranslationByRow
* @param {Object} options
* @param {Object} options.file
* @param {Object} options.row
* @param {Object} options.type
*/
trans.trigger("clearContextTranslationByRow", {file:trans.getSelectedId(), row:trans.grid.getSelectedRange(), type:"range"});
},
hidden: function() {
if (trans.grid.isColumnHeaderSelected()) return true;
if (trans.grid.isRowHeaderSelected()) return false;
return false;
}
},
"sep2": '---------',
'createAutomation' : {
name: t("Create Automation"),
callback: function(origin, selection, e) {
console.log(arguments);
var options = {
workspace: "gridSelection",
cellRange: trans.grid.getSelectedRange()
}
ui.openAutomationEditor("codeEditor_gridSelection", options);
}
},
'runAutomation' : {
name: ()=> {
trans.updateRunScriptGridMenu();
return t("Run Automation");
}
},
"sep3": '---------',
'properties': {
name: t("Row properties"),
callback: function(origin, selection, e) {
console.log(arguments);
ui.openRowProperties();
},
hidden: function() {
if (trans.grid.isColumnHeaderSelected()) return true;
if (trans.grid.isRowHeaderSelected()) return false;
return false;
}
}
/*
,
"colors": { // Own custom option
name: 'Colors...',
submenu: {
// Custom option with submenu of items
items: [
{
// Key must be in the form "parent_key:child_key"
key: 'colors:red',
name: 'Red',
callback: function(key, selection, clickEvent) {
setTimeout(function() {
alert('You clicked red!');
}, 0);
}
},
{ key: 'colors:green', name: 'Green' },
{ key: 'colors:blue', name: 'Blue' }
]
}
},
"credits": { // Own custom property
// Custom rendered element in the context menu
renderer: function(hot, wrapper, row, col, prop, itemValue) {
console.log("rendering credits");
console.log(arguments);
var elem = document.createElement('marquee');
elem.style.cssText = 'background: lightgray;';
elem.textContent = 'Brought to you by...';
return elem;
},
disableSelection: true, // Prevent mouseoever from highlighting the item for selection
isCommand: false // Prevent clicks from executing command and closing the menu
},
"about": { // Own custom option
name: function () { // `name` can be a string or a function
return '<b>Custom option</b>'; // Name can contain HTML
},
hidden: function () { // `hidden` can be a boolean or a function
// Hide the option when the first column was clicked
console.log(trans.grid.isColumnHeaderSelected());
if (trans.grid.isColumnHeaderSelected()) return false;
//return this.getSelectedLast()[1] == 0; // `this` === hot3
return true;
},
callback: function(key, selection, clickEvent) { // Callback for specific option
setTimeout(function() {
alert('Hello world!'); // Fire alert after menu close (with timeout)
}, 0);
}
}
*/
}
// backup current settings for close / new project actions
//var transTemplate = JSON.parse(JSON.stringify(trans));
trans.fileLoader = new FileLoader();
trans.fileLoader.add("json", function(path) {
trans.open(path);
})
trans.fileLoader.add("trans", function(path) {
trans.open(path);
})
trans.fileLoader.add("tpp", function(path) {
//trans.importTpp(openedFile);
trans.importTpp(path);
})
/**
* Attachment object.
* Located at trans.project.attachments
* @class
* @param {Object} obj
*/
window.Attachment = function(obj) {
obj = obj || {};
Object.assign(this, obj);
}
// ==============================================================
//
// E V E N T S
//
// ==============================================================
$(document).ready(function() {
if ($('body').is('[data-window="trans"]') == false) return;
trans.fileSelectorContextMenuInit();
//trans.gridBodyContextMenu();
const windowOnResize = function() {
$(document).trigger("windowResizeStop");
trans.grid.render();
ui.fixCellInfoSize();
}
$(window).resize(debounce(windowOnResize, 100));
trans.initTable();
trans.isLoaded = true;
// project level button initialization
trans.on("transLoaded", async ()=> {
console.warn("Trans loaded");
$(".menu-button > .button-gridInfo.gridInfo").removeClass("checked")
if (trans.getOption("gridInfo")?.isRuleActive) $(".menu-button > .button-gridInfo.gridInfo").addClass("checked")
})
});