Number to Words Conversion for OnlyOffice (=NOMBREENLETTRES)

Number to Words Conversion for OnlyOffice

Hey everyone,

I needed a function to convert numbers into words for invoices using the Algerian Dinar, so I developed this script with the help of ChatGPT. Since it might be useful for others, I’m sharing it here as my small contribution.

Demonstration:

This function:
:white_check_mark: Converts numbers to words in French
:white_check_mark: Supports large numbers (up to billions)
:white_check_mark: Adds “Dinar(s) & Centime(s)” for currency formatting
:white_check_mark: Works smoothly in OnlyOffice

Feel free to use and improve it! Hope it helps someone. :blush:

(function() {
    /**
     * Convertit un nombre en lettres avec la devise "dinar".
     * Compatible avec OnlyOffice.
     * Auteur: (ChatGPT)
     * @customfunction
     * @param {number} Nb Le nombre à convertir.
     * @returns {string} Le nombre en lettres avec la devise.
     */
    function nombreEnLettres(Nb) {
        let Chiffre = ["", "Un", "Deux", "Trois", "Quatre", "Cinq", "Six", "Sept", "Huit", "Neuf", "Dix", "Onze", "Douze", "Treize", "Quatorze", "Quinze", "Seize", "Dix-Sept", "Dix-Huit", "Dix-Neuf"];
        let dizaine = ["", "Dix", "Vingt", "Trente", "Quarante", "Cinquante", "Soixante", "", "Quatre-Vingt"];
        let resultat = "";
        let Devise = "Dinar";

        if (Nb === 0) return "Zéro " + Devise;

        // 🔥 FIX: Ensure proper rounding to avoid floating-point errors
        Nb = parseFloat(Nb.toFixed(2));

        function centaineDizaine(num) {
            let varlet = "";
            if (num >= 100) {
                varlet += (num >= 200 ? Chiffre[Math.floor(num / 100)] + " Cent " : "Cent ");
                num %= 100;
            }
            if (num < 20) {
                varlet += Chiffre[num];
            } else {
                let varnumD = Math.floor(num / 10);
                let varnumU = num % 10;
                if (varnumD === 7 || varnumD === 9) {
                    varlet += dizaine[varnumD - 1] + "-" + Chiffre[varnumU + 10];
                } else {
                    varlet += dizaine[varnumD] + (varnumU ? "-" + Chiffre[varnumU] : "");
                }
            }
            return varlet.trim();
        }

        let entier = Math.floor(Nb);
        let centimes = Math.round((Nb - entier) * 100);

        let milliards = Math.floor(entier / 1000000000);
        if (milliards > 0) {
            resultat += centaineDizaine(milliards) + " Milliard" + (milliards > 1 ? "s " : " ");
        }

        let millions = Math.floor((entier % 1000000000) / 1000000);
        if (millions > 0) {
            resultat += centaineDizaine(millions) + " Million" + (millions > 1 ? "s " : " ");
        }

        let milliers = Math.floor((entier % 1000000) / 1000);
        if (milliers > 0) {
            resultat += (milliers > 1 ? centaineDizaine(milliers) + " " : "") + "Mille ";
        }

        let reste = entier % 1000;
        if (reste > 0) {
            resultat += centaineDizaine(reste) + " ";
        }

        resultat = resultat.trim() + " " + Devise + (entier > 1 ? "s" : "");

        if (centimes > 0) {
            resultat += " & " + centaineDizaine(centimes) + " Centime" + (centimes > 1 ? "s" : "");
        }

        return resultat;
    }

    Api.AddCustomFunction(nombreEnLettres);
})();

Note:
The script works fine, but when exporting to PDF, a #NAME? error appears as if the function never existed.

Possible reasons:

  • Custom functions are not recalculated during export.
  • The function is missing in the exporting process.

Workaround – Convert to Values Before Exporting:

  1. Select the column where your function is applied.
  2. Copy (Ctrl + C).
  3. Paste as values (Ctrl + Shift + V or use “Paste Special” → “Values Only”) in the same cells. (This removes the custom function but keeps the calculated values.)
  4. Export the sheet to PDF.
2 Likes

Hi @Yassine :hugs:

Thank you for sharing your custom function! It’s a great contribution, and I’m sure it will be helpful to many users.

I’ve made some adjustments to your post and added a demonstration of your function in action (=NOMBREENLETTRES).

If you have the opportunity, could you create a separate topic describing or demonstrating the issue with exporting to PDF? We’d be happy to take a closer look and try to resolve it.

Looking forward to your feedback!

1 Like

Issue: Incorrect Number-to-Words Conversion for Certain Values

Thank you @Nikolas
I have noticed on the video demonstration a weird behavior when calculating values dynamically.

:mag: The Problem

When using a formula like A2 = A1 + 0.1, some numbers would convert incorrectly. For example:

Value Expected Output Actual Output
12.8 Douze Dinars & Quatre-Vingt Centimes :white_check_mark: Correct
12.9 Douze Dinars & Quatre-Vingt-Dix Centimes :white_check_mark: Correct
13.0 Treize Dinars :x: Incorrect: “Douze Dinars & Cent Centimes” translate; “Twelve Dinars & One hundred Cent”
14.0 Quatorze Dinars :x: Incorrect: “Treize Dinars & Cent Centimes” translate; “Thirteen Dinars & One hundred Cent”

This issue occurred only when numbers were generated through calculations (e.g., adding 0.1), while manually entering “13” or “14” worked correctly.

:bulb: The Cause: Floating-Point Precision Errors

The issue was caused by floating-point precision errors in JavaScript. When adding decimals (e.g., 13.0 + 0.1), the result might internally be stored as 12.9999999999 instead of 13, which led to incorrect conversions.

:wrench: The Fix

To solve this, I updated the script by adding explicit rounding before processing the number:

Summary
(function() {
    /**
     * Convertit un nombre en lettres avec la devise "dinar".
     * Compatible avec OnlyOffice.
     * Auteur: (ChatGPT)
     * @customfunction
     * @param {number} Nb Le nombre à convertir.
     * @returns {string} Le nombre en lettres avec la devise.
     */
    function nombreEnLettres(Nb) {
        let Chiffre = ["", "Un", "Deux", "Trois", "Quatre", "Cinq", "Six", "Sept", "Huit", "Neuf", "Dix", "Onze", "Douze", "Treize", "Quatorze", "Quinze", "Seize", "Dix-Sept", "Dix-Huit", "Dix-Neuf"];
        let dizaine = ["", "Dix", "Vingt", "Trente", "Quarante", "Cinquante", "Soixante", "", "Quatre-Vingt"];
        let resultat = "";
        let Devise = "Dinar";

        if (Nb === 0) return "Zéro " + Devise;

        // 🔥 FIX: Ensure proper rounding to avoid floating-point errors
        Nb = parseFloat(Nb.toFixed(2));

        function centaineDizaine(num) {
            let varlet = "";
            if (num >= 100) {
                varlet += (num >= 200 ? Chiffre[Math.floor(num / 100)] + " Cent " : "Cent ");
                num %= 100;
            }
            if (num < 20) {
                varlet += Chiffre[num];
            } else {
                let varnumD = Math.floor(num / 10);
                let varnumU = num % 10;
                if (varnumD === 7 || varnumD === 9) {
                    varlet += dizaine[varnumD - 1] + "-" + Chiffre[varnumU + 10];
                } else {
                    varlet += dizaine[varnumD] + (varnumU ? "-" + Chiffre[varnumU] : "");
                }
            }
            return varlet.trim();
        }

        let entier = Math.floor(Nb);
        let centimes = Math.round((Nb - entier) * 100);

        let milliards = Math.floor(entier / 1000000000);
        if (milliards > 0) {
            resultat += centaineDizaine(milliards) + " Milliard" + (milliards > 1 ? "s " : " ");
        }

        let millions = Math.floor((entier % 1000000000) / 1000000);
        if (millions > 0) {
            resultat += centaineDizaine(millions) + " Million" + (millions > 1 ? "s " : " ");
        }

        let milliers = Math.floor((entier % 1000000) / 1000);
        if (milliers > 0) {
            resultat += (milliers > 1 ? centaineDizaine(milliers) + " " : "") + "Mille ";
        }

        let reste = entier % 1000;
        if (reste > 0) {
            resultat += centaineDizaine(reste) + " ";
        }

        resultat = resultat.trim() + " " + Devise + (entier > 1 ? "s" : "");

        if (centimes > 0) {
            resultat += " & " + centaineDizaine(centimes) + " Centime" + (centimes > 1 ? "s" : "");
        }

        return resultat;
    }

    Api.AddCustomFunction(nombreEnLettres);
})();

This guarantees that numbers like 13.0 stay 13.0 and do not get misinterpreted.

:white_check_mark: Results After Fix

Now, all numbers are converted correctly, whether typed manually or calculated dynamically.

If you are using this script for invoices, I recommend updating it with this fix to avoid issues with centimes and rounding errors!

2 Likes

Hello @Nikolas
I have created a new topic as you asked me for… here is the link; Bug Report: Custom Function Error (#NAME?) When Exporting to PDF
Bext regards :slightly_smiling_face:

1 Like

Salam Yassine
Can I ask for Arabic conversion? Thank you

Hey! The Arabic number-to-words script is already done. However, due to OnlyOffice’s current RTL limitations, proper functionality might be affected. Full RTL support is expected in version 8.4, so you might need to wait for that to use it seamlessly. Let me know if you still want to check it out!

Moderators
Should I open a new topic and share it now, or would it be better to wait for the update to avoid potential frustration?

Hi @Yassine

I can’t give you an exact release date for version 8.4, but it’s expected around summer. There will definitely be improvements in RTL support.

That being said, any contribution from you is welcome, no matter which option you choose! Personally, I would wait for 8.4, but if you want to share it now, it could still be helpful :smiley:

1 Like