Skip to content

How to implement new language?

Julia Glaszka edited this page Sep 11, 2023 · 6 revisions

Contents

Adding new language can be easy or more demanding - depending on how numeral system in your language is similar to other languages (english, arabic, german, slavic etc.) and how many grammar exceptions it have.

How to implement changes?

Step 1: Implement unit tests

Test-Driven Development

We encourage you to create unit tests first before you start write code. It is named Test-Driven Development and is known as good engineering practice - you can read more about TDD paradigm here. Creating tests will be really helpful for you, because after every change in code you can just hit the button and see if your code works properly in every corner case.

Adjust the unit tests to your needs

You can copy below code to a new groovy file src/test/groovy/pl/allegro/finance/tradukisto/internal/languages/{yourLanguage}/{yourLanguage}ValuesTest.groovy and change translations to your language.

package pl.allegro.finance.tradukisto.internal.languages.english // todo: change it to your language

import spock.lang.Specification
import spock.lang.Unroll

// todo: below import your language container, it will be red because file does not exist yet, but dont worry about it now
import static pl.allegro.finance.tradukisto.internal.Container.englishContainer 

class EnglishValuesTest extends Specification {

    static intConverter = englishContainer().getIntegerConverter() // todo: use your language container
    static longConverter = englishContainer().getLongConverter() // todo: use your language container

    @Unroll
    def "should convert #value to '#words' in English"() { // todo: change name of your language
        expect:
        intConverter.asWords(value) == words

        where:
        value         | words
        0             | "zero" // todo: update every word to match the number in your language
        1             | "one"
        2             | "two"
        3             | "three"
        4             | "four"
        5             | "five"
        6             | "six"
        7             | "seven"
        8             | "eight"
        9             | "nine"

        11            | "eleven"
        12            | "twelve"
        13            | "thirteen"
        14            | "fourteen"
        15            | "fifteen"
        16            | "sixteen"
        17            | "seventeen"
        18            | "eighteen"
        19            | "nineteen"

        10            | "ten"
        20            | "twenty"
        30            | "thirty"
        40            | "forty"
        50            | "fifty"
        60            | "sixty"
        70            | "seventy"
        80            | "eighty"
        90            | "ninety"

        21            | "twenty-one"
        37            | "thirty-seven"
        43            | "forty-three"
        58            | "fifty-eight"
        69            | "sixty-nine"
        76            | "seventy-six"
        82            | "eighty-two"
        95            | "ninety-five"

        100           | "one hundred"
        200           | "two hundred"
        300           | "three hundred"
        400           | "four hundred"
        500           | "five hundred"
        600           | "six hundred"
        700           | "seven hundred"
        800           | "eight hundred"
        900           | "nine hundred"

        111           | "one hundred eleven"
        272           | "two hundred seventy-two"
        387           | "three hundred eighty-seven"
        448           | "four hundred forty-eight"
        569           | "five hundred sixty-nine"
        625           | "six hundred twenty-five"
        782           | "seven hundred eighty-two"
        895           | "eight hundred ninety-five"
        999           | "nine hundred ninety-nine"

        1_000         | "one thousand"
        2_000         | "two thousand"
        3_000         | "three thousand"
        4_000         | "four thousand"
        5_000         | "five thousand"
        7_634         | "seven thousand six hundred thirty-four"
        11_000        | "eleven thousand"
        15_000        | "fifteen thousand"
        21_000        | "twenty-one thousand"
        24_190        | "twenty-four thousand one hundred ninety"
        653_000       | "six hundred fifty-three thousand"
        123_454       | "one hundred twenty-three thousand four hundred fifty-four"
        700_000       | "seven hundred thousand"
        999_999       | "nine hundred ninety-nine thousand nine hundred ninety-nine"

        1_000_000     | "one million"
        2_000_000     | "two million"
        5_000_000     | "five million"
        23_437_219    | "twenty-three million four hundred thirty-seven thousand two hundred nineteen"
        100_000_000   | "one hundred million"
        123_456_789   | "one hundred twenty-three million four hundred fifty-six thousand seven hundred eighty-nine"
        322_089_890   | "three hundred twenty-two million eighty-nine thousand eight hundred ninety"

        1_000_000_000 | "one billion"
        2_147_483_647 | "two billion one hundred forty-seven million four hundred eighty-three thousand six hundred " +
                "forty-seven"
    }

    @Unroll
    def "should convert long #value to '#words' in English"() { // todo: change name of your language
        expect:
        longConverter.asWords(value) == words

        where:
        value                     | words
        5_000_000_000             | "five billion" // todo: update every word to match the number in your language

        1_000_000_000_000         | "one trillion"
        2_000_000_000_000         | "two trillion"
        5_000_000_000_000         | "five trillion"

        1_000_000_000_000_000     | "one quadrillion"
        2_000_000_000_000_000     | "two quadrillion"
        5_000_000_000_000_000     | "five quadrillion"

        1_000_000_000_000_000_000 | "one quintillion"
        2_000_000_000_000_000_000 | "two quintillion"
        Long.MAX_VALUE            | "nine quintillion two hundred twenty-three quadrillion " +
                                    "three hundred seventy-two trillion thirty-six billion " +
                                    "eight hundred fifty-four million seven hundred seventy-five thousand " +
                                    "eight hundred seven"
    }
}

Edit additional tests

List of the tests:

How to run your new tests?

You can run unit tests in terminal with ./gradlew test or green button in Intellij Idea code editor. Currently it will not compile until you will create dedicated Container for your language.

Step 2: Create the Container for new language

Implementation of simple container

New language needs to have implementation for Container. Easiest implementation of this looks like that:

// in Container.java class
   public static Container englishContainer() {
        return new Container(new EnglishValues());
    }

Implementation of more advanced container

Depending on how the language works or having conjugation, gender forms, custom chunking (typically divider is for 3 numbers, but for India is 2) etc. it may be needed to define more advanced container with custom implementations for counting. Code example for more advanced container can look like:

    public static Container italianContainer() {
        ItalianValues values = new ItalianValues();

        ItalianThousandToWordsConverter italianThousandToWordsConverter = new ItalianThousandToWordsConverter(
                values.baseNumbers());

        IntegerToStringConverter converter = new ItalianIntegerToWordsConverter(
                new NumberToWordsConverter(italianThousandToWordsConverter, values.pluralForms()), values.exceptions(),
                italianThousandToWordsConverter);

        BigDecimalToStringConverter bigDecimalBankingMoneyValueConverter = new BigDecimalToBankingMoneyConverter(
                converter, values.currency());

        return new Container(converter, null, bigDecimalBankingMoneyValueConverter);
    }

How to decide which Container do you need to use?

If you don't know how to classify your language, start from the easy implementation. Run tests and check if they passes. If not - you need to implement custom Converters (about it I will talk later).

Step 3: Create Values

Create {Language}Values.java file

Create a file in package pl.allegro.finance.tradukisto.internal.languages.{yourLanguage} with name {YourLanguage}Values.java (for example EnglishValues.java):

package pl.allegro.finance.tradukisto.internal.languages.english; // change package

import pl.allegro.finance.tradukisto.internal.BaseValues;
import pl.allegro.finance.tradukisto.internal.languages.GenderForms;
import pl.allegro.finance.tradukisto.internal.languages.PluralForms;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import static pl.allegro.finance.tradukisto.internal.support.BaseNumbersBuilder.baseNumbersBuilder;

public class EnglishValues implements BaseValues { // todo: change name of this class

    @Override
    public Map<Integer, GenderForms> baseNumbers() {
      //todo: implement
    }

    @Override
    public List<PluralForms> pluralForms() {
            //todo: implement
    }

    @Override
    public String currency() {
          //todo: implement
    }

    @Override
    public char twoDigitsNumberSeparator() {
        //todo: implement
    }
}

Define needed Values:

  1. translations for base numbers:
  @Override
    public Map<Integer, GenderForms> baseNumbers() {
        return baseNumbersBuilder()
                .put(0, "zero")
                .put(1, "one")
                .put(2, "two")
                .put(3, "three")
                .put(4, "four")
                .put(5, "five")
                .put(6, "six")
                .put(7, "seven")
                .put(8, "eight")
                .put(9, "nine")
                .put(10, "ten")
                .put(11, "eleven")
                .put(12, "twelve")
                .put(13, "thirteen")
                .put(14, "fourteen")
                .put(15, "fifteen")
                .put(16, "sixteen")
                .put(17, "seventeen")
                .put(18, "eighteen")
                .put(19, "nineteen")
                .put(20, "twenty")
                .put(30, "thirty")
                .put(40, "forty")
                .put(50, "fifty")
                .put(60, "sixty")
                .put(70, "seventy")
                .put(80, "eighty")
                .put(90, "ninety")
                .put(100, "one hundred")
                .put(200, "two hundred")
                .put(300, "three hundred")
                .put(400, "four hundred")
                .put(500, "five hundred")
                .put(600, "six hundred")
                .put(700, "seven hundred")
                .put(800, "eight hundred")
                .put(900, "nine hundred")
                .build();
    }
  1. translations for plural forms (thousands, millions etc):
   public List<PluralForms> pluralForms() {
        return Arrays.asList(
                new EnglishPluralForms(""),
                new EnglishPluralForms("thousand"),
                new EnglishPluralForms("million"),
                new EnglishPluralForms("billion"),
                new EnglishPluralForms("trillion"),
                new EnglishPluralForms("quadrillion"),
                new EnglishPluralForms("quintillion"));
    }
  1. Define default currency
    @Override
    public String currency() {
        return "£";
    }
  1. Define twoDigitsNumberSeparator:
    // defines if numbers should be separated by space or other character (for example 22=twenty-two (english separator is `-`), 22=dwadzieścia dwa (polish has space separator ` `)).
    @Override
    public char twoDigitsNumberSeparator() { 
        return '-';
    }

Step 4: Push changes to the Github and create Pull Request

You can run tests and check if they passes. If yes, you can create your first Pull Request .

Feel free to ask if you have more questions, we will try to help you. https://github.com/allegro/tradukisto/wiki/How-to-start-contributing