Custom SpellNumber

Hi Ragicians!

I’m in a bit of a bind. The out-of-the-box SPELLNUMBER does not cover my locale (TH), so I decided to create a JS workflow that will read positive integers in Thai.

Global JS: (because this is shared between many sheets)

function decimalExpansionArray(val) {
  //expands val into array of digits, from rightmost, assuming that val > 0
  let valToRead = val;
  let expansion = new Array();
  while (valToRead > 0) {
    expansion.push(valToRead % 10);
    valToRead = (valToRead - (valToRead % 10))/10;
  }
  return expansion;
}

function readCurrentDigitTh(digit, exponent) {
  //reads the current digit in Thai
  //e.g., the 2 in 21 is read ยี่สิบ
  const digits = ['', 'หนึ่ง', 'สอง', 'สาม', 'สี่', 'ห้า', 'หก', 'เจ็ด', 'แปด', 'เก้า'];//0 is never read, and digits correspond to indices
  const exponents = ['', 'สิบ', 'ร้อย', 'พัน', 'หมื่น', 'แสน', 'ล้าน']; //no word for the units digit
  let readDigit = '';
  if (digit == 0) {
    return readDigit;
  }
  readDigit = digits[digit] + exponents[exponent];
  readDigit = readDigit.replace('หนึ่งสิบ', 'สิบ');
  readDigit = readDigit.replace('สองสิบ', 'ยี่สิบ');
  return readDigit;
}

function spellNumberTh(val) {
  //reads val in Thai
  if (val == 0) {
    return 'ศูนย์';
  }
  return decimalExpansionArray(val).map(readCurrentDigitTh).reduceRight((accumulator, currentValue) => accumulator.concat(currentValue),);
}

Sheet-specific JS: (post-workflow because people will complain otherwise)

function setGrandTotalInThai() {
  let query = db.getAPIQuery("/ragicsales/10006");
  let entry = query.getAPIEntry(param.getNewNodeId(2000246));
  let grandTotalInWords = spellNumberTh(Math.round(entry.getFieldValue(2000245)));
  entry.setFieldValue(1000159, grandTotalInWords);
  entry.save();
}

However, when a record is saved, errors show up:

:3:6 Expected ; but found valToRead let valToRead = val; ^ in at line number 3 at column number 6
:50:6 Expected ; but found query let query = db.getAPIQuery("/ragicsales/10006"); ^ in at line number 50 at column number 6

and the field isn’t updated.

Running the global JS on JSFiddle works fine.

It could definitely use some work with numbers bigger than 10,000,000 and negative numbers, but a recursion should suffice.

Replacing all the let and const with var doesn’t help, so I’m at wit’s end. Could anybody help me? I would be very grateful.

From my experience, Ragic doesn’t take let and const.
I don’t read Thai, so I am only as correct as the understanding of my AI assistant on Thai Number in Text. :sweat_smile:

You can try the following code for conversion, but it doesn’t do number with decimal points.
I have test it upto 9 digit number. See if it helps.



// Digits and Exponents arrays outside of the functions
var digits = ['', 'หนึ่ง', 'สอง', 'สาม', 'สี่', 'ห้า', 'หก', 'เจ็ด', 'แปด', 'เก้า'];
var exponents = ['', 'สิบ', 'ร้อย', 'พัน', 'หมื่น', 'แสน', 'ล้าน'];

// Function to convert number to Thai words
function thaiSpellNumber(number) {
    // Split the number into integer and decimal parts
    var parts = number.toString().split('.');
    var integerPart = parts[0];
    var decimalPart = parts.length > 1 ? parts[1] : null;

    // Function to convert the integer part of the number
    function convertIntegerPart(num) {
        var result = '';
        var len = num.length;

        // Loop through each digit in the integer part
        for (var i = 0; i < len; i++) {
            var digit = parseInt(num[i]);
            var pos = len - i - 1; // Position from the right

            if (digit !== 0) {
                // Handle special cases for 'สิบ' (ten) and 'ยี่สิบ' (twenty)
                if (pos == 1 && digit == 1) {
                    result += 'สิบ'; // 'สิบ' means 'ten'
                } else if (pos == 1 && digit == 2) {
                    result += 'ยี่สิบ'; // 'ยี่สิบ' means 'twenty'
                } else if (pos == 1 && digit > 2) {
                    result += digits[digit] + 'สิบ'; // 'สิบ' means 'ten'
                // Handle the special case for 'เอ็ด' (one as a last digit)
                } else if (pos == 0 && digit == 1 && len > 1 && num[len - 2] != '0') {
                    result += 'เอ็ด'; // 'เอ็ด' means 'one' when it's the last digit
                } else {
                    result += digits[digit] + exponents[pos % 6];
                    // Append the corresponding exponent (unit) from exponents array
                }
            }

            // Append 'ล้าน' (million) for every six positions
            if (pos % 6 == 0 && pos > 0) {
                result += exponents[6];
            }
        }

        // Correct the positions of special cases
        result = result.replace('หนึ่งสิบสอง', 'สิบสอง');
        result = result.replace('สองสิบ', 'ยี่สิบ');
        result = result.replace('หนึ่งสิบ', 'สิบ');
        if (result.includes('หนึ่งร้อยหนึ่ง') && num.endsWith('01')) {
            result = result.replace('หนึ่งร้อยหนึ่ง', 'หนึ่งร้อยเอ็ด');
        }

        return result;
    }

    // Function to convert the decimal part of the number
    function convertDecimalPart(num) {
        var result = 'จุด'; // Thai word for 'point' (decimal point)
        // Loop through each digit in the decimal part
        for (var i = 0; i < num.length; i++) {
            var digit = parseInt(num[i]);
            result += digits[digit]; // Append the corresponding Thai digit
        }
        return result;
    }

    // Convert the integer part and combine with the decimal part if present
    var thaiText = convertIntegerPart(integerPart);
    if (decimalPart) {
        thaiText += convertDecimalPart(decimalPart);
    }

    return thaiText;
}

// Function to be triggered to convert Number field to Thai Text field
function convertNumberToThaiText() {
    // Get the record that was just updated
    var entry = param.getUpdatedEntry();
    log.println("Retrieved entry: " + entry);

    // Field IDs
    var numberFieldId = 1027377;
    var thaiTextFieldId = 1027378;

    // Get the number value from the Number field
    var numberValue = entry.getFieldValue(numberFieldId);
    log.println("Number value: " + numberValue);

    // Convert the number to Thai text
    var thaiText = thaiSpellNumber(numberValue);
    log.println("Converted Thai text: " + thaiText);

    // Store the Thai text into the Thai Text field
    entry.setFieldValue(thaiTextFieldId, thaiText);

    // Save the entry to persist the changes
    entry.save();
}

// Execute the convertNumberToThaiText function
convertNumberToThaiText();

Thanks! I tried it, but it somehow didn’t really run.
When a record was saved, there was a modal window saying that the entry was saved, but then it stayed like that. The modal froze and didn’t go away as normal. The log (thank Ragic for that compared to that-which-must-not-be-named) didn’t show anything besides the updated number field, not even the log.println() stuff.
This also happened on my version as well, when let and const are changed to var. Did I mess up something?
Is it related to my account being a trial account? If so, I’ll do this later when I finally subscribe (in a few days).

Edit: just found the workflow execution log. Silly me. It says it found EOF. I’ll double check.

The code ran fine on my end though.
But I currently put that all in the post workflow on my test form.
It’s able to generate the result.

image

Not sure what cause you the EOF error.

Thanks a lot for your help. Here’s my take, and this should read integers of any length, except if string.repeat() isn’t available.

Global JS to share across sheets:

var digits = ['', 'หนึ่ง', 'สอง', 'สาม', 'สี่', 'ห้า', 'หก', 'เจ็ด', 'แปด', 'เก้า'];
var exponents = ['', 'สิบ', 'ร้อย', 'พัน', 'หมื่น', 'แสน'];

// Function to convert number to Thai words
function thaiSpellNumber(number) {
   //reads integers in Thai
  function expandDecimal(num) {
    //expands the given number as its decimal expansion and returns an array
    var valToRead = num;
    var expansion = [];
    while (valToRead > 0) {
      expansion.push(valToRead % 10);
      valToRead = (valToRead - (valToRead % 10))/10;
    }
    return expansion;
  }
  function fillExponents(toLength) {
    //
    var arrToRepeat = new Array(toLength);
    var million = 'ล้าน';
    for (var i = 0; i < toLength; i++) {
      arrToRepeat[i] = (i % 6 == 0) ? million.repeat(i/6) : exponents[i % 6];
    }
    return arrToRepeat;
  }
  var expansionToRead = expandDecimal(number);
  for (var i = 0; i < expansionToRead.length; i++) {
    expansionToRead[i] = digits[expansionToRead[i]];
  }
  var places = fillExponents(expansionToRead.length);
  for (var i = 0; i < places.length; i++) {
    if (expansionToRead[i] == '') {
      continue;
    }
    expansionToRead[i] = expansionToRead[i] + places[i];
  }
  var numInWords = '';
  for (var i = 0; i < expansionToRead.length; i++) {
    numInWords = expansionToRead[i] + numInWords;
  }
  numInWords = numInWords.replace('สิบหนึ่ง', 'สิบเอ็ด');
  numInWords = numInWords.replace('หนึ่งสิบ', 'สิบ');
  numInWords = numInWords.replace('สองสิบ', 'ยี่สิบ');
  return numInWords;
}

Sheet-specific JS:

function convertNumberToThaiText() {
    // Get the record that was just updated
    var entry = param.getUpdatedEntry();
    // Field IDs
    var grandTotalFieldId = 2000245;
    var grandTotalInWordsId = 1000159;
    // Get the number value from the Number field
    var numberValue = entry.getFieldValue(grandTotalFieldId);
    // Convert the number to Thai text
    var thaiText = thaiSpellNumber(numberValue);

    // Store the Thai text into the Thai Text field
    entry.setFieldValue(grandTotalInWordsId, thaiText);

    // Save the entry to persist the changes
    entry.save();
}