/* eslint-disable camelcase */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable quote-props */
/* istanbul ignore file */

const keyword = (palabra: string): string => 'la palabra clave "' + palabra + '"';

function pluralize(n: number, singular: string, plural: string) {
    if (n === 0) {
        return 'ningún ' + singular;
    } else if (n === 1) {
        return 'un ' + singular;
    } else {
        return n.toString() + ' ' + plural;
    }
}

// Only for typing purposes
// eslint-disable-next-line @typescript-eslint/ban-types
const toFunc = (x: string | Function): Function => x as Function;

function ordinalNumber(n: number): string {
    const units = [
        '',
        'primer',
        'segundo',
        'tercer',
        'cuarto',
        'quinto',
        'sexto',
        'séptimo',
        'octavo',
        'noveno'
    ];
    if (n >= 1 && n <= 9) {
        return units[n];
    } else {
        return '#' + n.toString();
    }
}

function describeType(type: any): string[] {
    if (type.isInteger()) {
        return ['m', 'número', 'números'];
    } else if (type.isBoolean()) {
        return ['m', 'booleano', 'booleanos'];
    } else if (type.isColor()) {
        return ['m', 'color', 'colores'];
    } else if (type.isDirection()) {
        return ['f', 'dirección', 'direcciones'];
    } else if (type.isList() && type.contentType.isAny()) {
        return ['f', 'lista', 'listas'];
    } else if (type.isList()) {
        const description = describeType(type.contentType);
        if (description === undefined) {
            return undefined;
        } else {
            const plural = description[2];
            return ['f', 'lista de ' + plural, 'listas de ' + plural];
        }
    } else {
        return undefined;
    }
}

function describeTypeSingular(type: { toString: () => string }): string {
    const description = describeType(type);
    if (description === undefined) {
        return type.toString();
    } else {
        const singular = description[1];
        return singular;
    }
}

function typeAsNoun(type: { toString: () => string }): string {
    const description = describeType(type);
    if (description === undefined) {
        return 'un valor de tipo ' + type.toString();
    } else {
        const gender = description[0];
        const singular = description[1];
        if (gender === 'm') {
            return 'un ' + singular;
        } else {
            return 'una ' + singular;
        }
    }
}

function typeAsQualifierSingular(type: { toString: () => string }): string {
    const description = describeType(type);
    if (description === undefined) {
        return 'de tipo ' + type.toString();
    } else {
        const gender = description[0];
        const singular = description[1];
        if (gender === 'm') {
            return 'un ' + singular;
        } else {
            return 'una ' + singular;
        }
    }
}

function typeAsQualifierPlural(type: { toString: () => string }): string {
    const description = describeType(type);
    if (description === undefined) {
        return 'de tipo ' + type.toString();
    } else {
        const gender = description[0];
        const plural = description[2];
        if (gender === 'm') {
            return plural;
        } else {
            return plural;
        }
    }
}

function listOfTypes(types: any) {
    const typeStrings = [];
    for (const type of types) {
        typeStrings.push(describeTypeSingular(type));
    }
    return typeStrings.join(', ');
}

function openingDelimiterName(delimiter: string) {
    if (delimiter === '(' || delimiter === ')') {
        return 'un paréntesis abierto "("';
    } else if (delimiter === '[' || delimiter === ']') {
        return 'un corchete abierto "["';
    } else if (delimiter === '{' || delimiter === '}') {
        return 'una llave abierta "{"';
    } else {
        return delimiter;
    }
}

function formatTypes(string: string | any[], type1: any, type2: any) {
    let result = '';
    for (let i = 0; i < string.length; i++) {
        if (string[i] === '%' && i + 1 < string.length) {
            if (string[i + 1] === '%') {
                result += '%';
                i++;
            } else if (string[i + 1] === '1') {
                result += typeAsNoun(type1);
                i++;
            } else if (string[i + 1] === '2') {
                result += typeAsNoun(type2);
                i++;
            } else {
                result += '%';
            }
        } else {
            result += string[i];
        }
    }
    return result;
}

// eslint-disable-next-line @typescript-eslint/ban-types
export const LOCALE_ES: Record<string, string | Function> = {
    /* Descriptions of syntactic constructions and tokens */
    definition: 'una definición (de programa, función, procedimiento, o tipo)',
    pattern: 'un patrón (comodín "_", constructor aplicado a variables, o tupla)',
    statement: 'un comando',
    expression: 'una expresión',
    'procedure call': 'una invocación a un procedimiento',
    'field name': 'el nombre de un campo',
    T_EOF: 'el final del archivo',
    T_NUM: 'un número',
    T_STRING: 'una cadena (string)',
    T_UPPERID: 'un identificador con mayúsculas',
    T_LOWERID: 'un identificador con minúsculas',
    T_PROGRAM: keyword('program'),
    T_INTERACTIVE: keyword('interactive'),
    T_PROCEDURE: keyword('procedure'),
    T_FUNCTION: keyword('function'),
    T_RETURN: keyword('return'),
    T_IF: keyword('if'),
    T_THEN: keyword('then'),
    T_ELSE: keyword('else'),
    T_REPEAT: keyword('repeat'),
    T_FOREACH: keyword('foreach'),
    T_IN: keyword('in'),
    T_WHILE: keyword('while'),
    T_SWITCH: keyword('switch'),
    T_TO: keyword('to'),
    T_LET: keyword('let'),
    T_NOT: keyword('not'),
    T_DIV: keyword('div'),
    T_MOD: keyword('mod'),
    T_TYPE: keyword('type'),
    T_IS: keyword('is'),
    T_CHOOSE: keyword('choose'),
    T_WHEN: keyword('when'),
    T_OTHERWISE: keyword('otherwise'),
    T_MATCHING: keyword('matching'),
    T_SELECT: keyword('select'),
    T_ON: keyword('on'),
    T_RECORD: keyword('record'),
    T_VARIANT: keyword('variant'),
    T_CASE: keyword('case'),
    T_FIELD: keyword('field'),
    T_UNDERSCORE: 'un guión bajo ("_")',
    T_LPAREN: 'un paréntesis izquierdo ("(")',
    T_RPAREN: 'un paréntesis derecho (")")',
    T_LBRACE: 'una llave izquierda ("{")',
    T_RBRACE: 'una llave derecha ("}")',
    T_LBRACK: 'un corchete izquierdo ("[")',
    T_RBRACK: 'un corchete derecho ("]")',
    T_COMMA: 'una coma (",")',
    T_SEMICOLON: 'un punto y coma (";")',
    T_RANGE: 'un separador de rango ("..")',
    T_GETS: 'una flecha hacia la izquierda ("<-")',
    T_PIPE: 'una barra vertical ("|")',
    T_ARROW: 'una flecha ("->")',
    T_ASSIGN: 'un operador de asignación (":=")',
    T_EQ: 'una comparación por igualdad ("==")',
    T_NE: 'una comparación por desigualdad ("/=")',
    T_LE: 'un menor o igual ("<=")',
    T_GE: 'un mayor o igual (">=")',
    T_LT: 'un menor estricto ("<")',
    T_GT: 'un mayor estricto (">")',
    T_AND: 'el "y" lógico ("&&")',
    T_OR: 'el "o" lógico ("||")',
    T_CONCAT: 'el operador de concatenación de listas ("++")',
    T_PLUS: 'el operador de suma ("+")',
    T_MINUS: 'el operador de resta ("-")',
    T_TIMES: 'el operador de producto ("*")',
    T_POW: 'el operador de potencia ("^")',

    /* Local name categories */
    LocalVariable: 'variable',
    LocalIndex: 'índice',
    LocalParameter: 'parámetro',

    /* Descriptions of value types */
    V_Integer: 'un número',
    V_String: 'una cadena',
    V_Tuple: 'una tupla',
    V_List: 'una lista',
    V_Structure: 'una estructura',

    /* Lexer */
    'errmsg:unclosed-multiline-comment': 'El comentario se abre pero nunca se cierra.',

    'errmsg:unclosed-string-constant':
        'La comilla que abre no tiene una comilla que cierra correspondiente.',

    // eslint-disable-next-line max-len
    'errmsg:numeric-constant-should-not-have-leading-zeroes': `Las constantes numéricas no se pueden escribir con ceros a la izquierda.`,

    // eslint-disable-next-line max-len
    'errmsg:identifier-must-start-with-alphabetic-character': `Los identificadores deben empezar con un caracter alfabético (a...z,A...Z).`,

    'errmsg:unknown-token': (symbol: string) =>
        'Símbolo desconocido en la entrada: "' + symbol + '".',

    'warning:empty-pragma': 'Directiva pragma vacía.',

    'warning:unknown-pragma': (pragmaName: string) =>
        'Directiva pragma desconocida: "' + pragmaName + '".',

    'errmsg:unmatched-opening-delimiter': (delimiter: any) =>
        'Se encontró ' + openingDelimiterName(delimiter) + ' pero nunca se cierra.',

    'errmsg:unmatched-closing-delimiter': (delimiter: string) =>
        'Se encontró un "' +
        delimiter +
        '" ' +
        'pero no había ' +
        openingDelimiterName(delimiter) +
        '.',

    'errmsg:unknown-language-option': (option: string) => 'Opción desconocida. "' + option + '".',

    /* Parser */
    'errmsg:empty-source': 'El programa está vacío.',

    'errmsg:expected-but-found': (expected: string, found: string) =>
        `Se esperaba ${expected}. Se encontró: ${found}.`,

    'errmsg:pattern-number-cannot-be-negative-zero': 'El patrón numérico no puede ser "-0".',

    'errmsg:return-tuple-cannot-be-empty': 'El return tiene que devolver algo.',

    'errmsg:pattern-tuple-cannot-be-singleton':
        'El patrón para una tupla no puede tener una sola componente. ' +
        'Las tuplas tienen 0, 2, 3, o más componentes, pero no 1.',

    'errmsg:assignment-tuple-cannot-be-singleton':
        'La asignación a una tupla no puede constar de una sola componente. ' +
        'Las tuplas tienen 0, 2, 3, o más componentes, pero no 1.',

    'errmsg:operators-are-not-associative': (op1: string, op2: string) =>
        'La expresión usa ' +
        op1 +
        ' y ' +
        op2 +
        ', pero estos operadores no se pueden asociar. ' +
        'Quizás faltan paréntesis.',

    'errmsg:obsolete-tuple-assignment':
        'Se esperaba un comando pero se encontró un paréntesis izquierdo. ' +
        'Nota: la sintaxis de asignación de tuplas "(x1, ..., xN) := y" ' +
        'está obsoleta. Usar "let (x1, ..., xN) := y".',

    /* Linter */
    'errmsg:program-already-defined': (pos1: string, pos2: string) =>
        'Ya había un programa definido en ' +
        pos1 +
        '.\n' +
        'No se puede definir un programa en ' +
        pos2 +
        '.',

    'errmsg:procedure-already-defined': (name: string, pos1: string, pos2: string) =>
        'El procedimiento "' +
        name +
        '" está definido dos veces: ' +
        'en ' +
        pos1 +
        ' y en ' +
        pos2 +
        '.',

    'errmsg:function-already-defined': (name: string, pos1: string, pos2: string) =>
        'La función "' +
        name +
        '" está definida dos veces: ' +
        'en ' +
        pos1 +
        ' y en ' +
        pos2 +
        '.',

    'errmsg:type-already-defined': (name: string, pos1: string, pos2: string) =>
        `El tipo "${name}" está definido dos veces: en ${pos1} y en ${pos2}.`,

    'errmsg:constructor-already-defined': (name: string, pos1: string, pos2: string) =>
        'El constructor "' +
        name +
        '" está definido dos veces: ' +
        'en ' +
        pos1 +
        ' y en ' +
        pos2 +
        '.',

    'errmsg:repeated-field-name': (constructorName: string, fieldName: string) =>
        'El campo "' +
        fieldName +
        '" no puede estar repetido ' +
        'para el constructor "' +
        constructorName +
        '".',

    'errmsg:function-and-field-cannot-have-the-same-name': (
        name: string,
        posFunction: string,
        posField: string
    ) =>
        'El nombre "' +
        name +
        '" se usa ' +
        'para una función en ' +
        posFunction +
        ' y ' +
        'para un campo en ' +
        posField +
        '.',

    'errmsg:source-should-have-a-program-definition':
        /* Note: the code may actually be completely empty, but
         * we avoid this technicality since the message could be
         * confusing. */
        'El código debe tener una definición de "program { ... }".',

    'errmsg:procedure-should-not-have-return': (name: string) =>
        `El procedimiento "${name}" no debería tener un comando "return".`,

    'errmsg:function-should-have-return': (name: string) =>
        'La función "' + name + '" debería tener un comando "return".',

    'errmsg:return-statement-not-allowed-here':
        'El comando "return" solo puede aparecer como el último comando ' +
        'de una función o como el último comando del programa.',

    'errmsg:local-name-conflict': (
        name: string,
        oldCat: string,
        oldPos: string,
        newCat: string,
        newPos: string
    ) =>
        'Conflicto de nombres: "' +
        name +
        '" se usa dos veces: ' +
        'como ' +
        oldCat +
        ' en ' +
        oldPos +
        ', y ' +
        'como ' +
        newCat +
        ' en ' +
        newPos +
        '.',

    'errmsg:repeated-variable-in-tuple-assignment': (name: string) =>
        `La variable "${name}" está repetida en la asignación de tuplas.`,

    'errmsg:constructor-used-as-procedure': (name: string, type: string) =>
        'El procedimiento "' +
        name +
        '" no está definido. ' +
        'El nombre "' +
        name +
        '" es el nombre de un constructor ' +
        'del tipo "' +
        type +
        '".',

    'errmsg:undefined-procedure': (name: string) =>
        'El procedimiento "' + name + '" no está definido.',

    'errmsg:undefined-function': (name: string) => 'La función "' + name + '" no está definida.',

    'errmsg:procedure-arity-mismatch': (name: string, expected: any, received: any) =>
        '"El procedimiento "' +
        name +
        '" espera recibir ' +
        toFunc(LOCALE_ES['<n>-parameters'])(expected) +
        ' pero se lo invoca con ' +
        toFunc(LOCALE_ES['<n>-arguments'])(received) +
        '.',

    'errmsg:function-arity-mismatch': (name: string, expected: any, received: any) =>
        'La función "' +
        name +
        '" espera recibir ' +
        toFunc(LOCALE_ES['<n>-parameters'])(expected) +
        ' pero se la invoca con ' +
        toFunc(LOCALE_ES['<n>-arguments'])(received) +
        '.',

    'errmsg:structure-pattern-arity-mismatch': (name: string, expected: any, received: any) =>
        'El constructor "' +
        name +
        '" tiene ' +
        toFunc(LOCALE_ES['<n>-fields'])(expected) +
        ' pero el patrón tiene ' +
        toFunc(LOCALE_ES['<n>-parameters'])(received) +
        '.',

    'errmsg:type-used-as-constructor'(name: string, constructorNames: any[]) {
        let msg: string;
        if (constructorNames.length === 0) {
            msg = '(no tiene constructores).';
        } else if (constructorNames.length === 1) {
            msg = '(tiene un constructor: ' + constructorNames[0] + ').';
        } else {
            msg = '(sus constructores son: ' + constructorNames.join(', ') + ').';
        }
        return (
            'El constructor "' +
            name +
            '" no está definido. ' +
            'El nombre "' +
            name +
            '" es el nombre de un tipo ' +
            msg
        );
    },

    'errmsg:procedure-used-as-constructor': (name: string) =>
        'El constructor "' +
        name +
        '" no está definido. ' +
        'El nombre "' +
        name +
        '" es el nombre de un procedimiento.',

    'errmsg:undeclared-constructor': (name: string) =>
        'El constructor "' + name + '" no está definido.',

    'errmsg:wildcard-pattern-should-be-last': 'El comodín "_" debe estar en la última rama.',

    'errmsg:variable-pattern-should-be-last': (name: string) =>
        'El patrón variable "' + name + '" tiene debe estar en la última rama.',

    'errmsg:numeric-pattern-repeats-number': (number: string) =>
        'Hay dos ramas distintas para el número "' + number + '".',

    'errmsg:structure-pattern-repeats-constructor': (name: string) =>
        'Hay dos ramas distintas para el constructor "' + name + '".',

    'errmsg:structure-pattern-repeats-tuple-arity': (arity: { toString: () => string }) =>
        'Hay dos ramas distintas para las tuplas de ' + arity.toString() + ' componentes.',

    'errmsg:structure-pattern-repeats-timeout': 'Hay dos ramas distintas para el TIMEOUT.',

    'errmsg:pattern-does-not-match-type': (expectedType: string, patternType: string) =>
        'Los patrones tienen que ser todos del mismo tipo. ' +
        'El patrón debería ser de tipo ' +
        expectedType +
        'pero es de tipo ' +
        patternType +
        '.',

    'errmsg:patterns-in-interactive-program-must-be-events':
        'Los patrones de un "interactive program" deben ser eventos.',

    'errmsg:patterns-in-interactive-program-cannot-be-variables':
        'El patrón no puede ser una variable.',

    'errmsg:patterns-in-switch-must-not-be-events': 'El patrón no puede ser un evento.',

    'errmsg:structure-construction-repeated-field': (constructorName: string, fieldName: string) =>
        'El campo "' +
        fieldName +
        '" está repetido en ' +
        'la instanciación del constructor "' +
        constructorName +
        '".',

    'errmsg:structure-construction-invalid-field': (constructorName: string, fieldName: string) =>
        'El campo "' +
        fieldName +
        '" no es un campo válido ' +
        'para el constructor "' +
        constructorName +
        '".',

    'errmsg:structure-construction-missing-field': (constructorName: string, fieldName: string) =>
        'Falta darle valor al campo "' +
        fieldName +
        '" ' +
        'del constructor "' +
        constructorName +
        '".',

    'errmsg:structure-construction-cannot-be-an-event': (constructorName: string) =>
        'El constructor "' +
        constructorName +
        '" corresponde a un ' +
        'evento, y solamente se puede manejar implícitamente ' +
        'en un programa interactivo (el usuario no puede construir ' +
        'instancias).',

    'errmsg:forbidden-extension-destructuring-foreach':
        'El índice de la repetición indexada debe ser un identificador.',

    ['errmsg:forbidden-extension-allow-recursion']: (cycle: any) => {
        const msg = [];
        for (const call of cycle) {
            msg.push(
                '  ' +
                    call.caller +
                    ' llama a ' +
                    call.callee +
                    ' (' +
                    call.location.startPos.filename.toString() +
                    ':' +
                    call.location.startPos.line.toString() +
                    ':' +
                    call.location.startPos.column.toString() +
                    ')'
            );
        }
        return (
            'La recursión está deshabilitada. ' +
            'Hay un ciclo en las invocaciones:\n' +
            msg.join('\n')
        );
    },

    'errmsg:patterns-in-foreach-must-not-be-events':
        'El patrón de un foreach no puede ser un evento.',

    /* Runtime errors (virtual machine) */
    'errmsg:ellipsis': 'El programa todavía no está completo.',

    'errmsg:undefined-variable': (variableName: string) =>
        'La variable "' + variableName + '" no está definida.',

    'errmsg:too-few-arguments': (routineName: string) =>
        'Faltan argumentos para "' + routineName + '".',

    'errmsg:expected-structure-but-got': (constructorName: string, valueTag: string) =>
        'Se esperaba una estructura construida ' +
        'con el constructor "' +
        constructorName +
        '", ' +
        'pero se recibió ' +
        valueTag +
        '.',

    'errmsg:expected-constructor-but-got': (
        constructorNameExpected: string,
        constructorNameReceived: string
    ) =>
        'Se esperaba una estructura construida ' +
        'con el constructor "' +
        constructorNameExpected +
        '", ' +
        'pero el constructor recibido es ' +
        constructorNameReceived +
        '".',

    'errmsg:incompatible-types-on-assignment': (variableName: string, oldType: any, newType: any) =>
        'La variable "' +
        variableName +
        '" ' +
        'contenía ' +
        typeAsNoun(oldType) +
        ', ' +
        'no se le puede asignar ' +
        typeAsNoun(newType) +
        '".',

    'errmsg:incompatible-types-on-list-creation': (
        index: { toString: () => string },
        oldType: any,
        newType: any
    ) =>
        'Todos los elementos de una lista deben ser del mismo tipo. ' +
        'Los elementos son ' +
        typeAsQualifierPlural(oldType) +
        ', ' +
        'pero el elemento en la posición ' +
        index.toString() +
        ' ' +
        'es ' +
        typeAsQualifierSingular(newType) +
        '.',

    'errmsg:incompatible-types-on-structure-update': (
        fieldName: string,
        oldType: any,
        newType: any
    ) =>
        'El campo "' +
        fieldName +
        '" es ' +
        typeAsQualifierSingular(oldType) +
        '. ' +
        'No se lo puede actualizar con ' +
        typeAsNoun(newType) +
        '.',

    'errmsg:expected-tuple-value-but-got': (receivedType: any) =>
        'Se esperaba una tupla pero se recibió ' + typeAsNoun(receivedType) + '.',

    'errmsg:tuple-component-out-of-bounds': (
        size: { toString: () => string },
        index: { toString: () => string }
    ) =>
        'Índice fuera de rango. ' +
        'La tupla es de tamaño ' +
        size.toString() +
        ' y ' +
        'el índice es ' +
        index.toString() +
        '.',

    'errmsg:expected-structure-value-but-got': (receivedType: any) =>
        'Se esperaba una estructura pero se recibió ' + typeAsNoun(receivedType) + '.',

    'errmsg:structure-field-not-present': (fieldNames: any[], missingFieldName: string) =>
        'La estructura no tiene un campo "' +
        missingFieldName +
        '". ' +
        'Los campos son: [' +
        fieldNames.join(', ') +
        '].',

    'errmsg:primitive-does-not-exist': (primitiveName: string) =>
        `La operación primitiva "${primitiveName}" no existe o no está disponible.`,

    'errmsg:primitive-arity-mismatch': (name: string, expected: any, received: any) =>
        'La operación "' +
        name +
        '" espera recibir ' +
        toFunc(LOCALE_ES['<n>-parameters'])(expected) +
        ' pero se la invoca con ' +
        toFunc(LOCALE_ES['<n>-arguments'])(received) +
        '.',

    'errmsg:primitive-argument-type-mismatch'(
        name: string,
        parameterIndex: any,
        numArgs: number,
        expectedType: any,
        receivedType: any
    ) {
        let msg = 'El ';
        if (numArgs > 1) {
            msg += ordinalNumber(parameterIndex) + ' ';
        }
        msg += 'parámetro ';
        msg += 'de "' + name + '" ';
        msg += 'debería ser ' + typeAsQualifierSingular(expectedType) + ' ';
        msg += 'pero es ' + typeAsQualifierSingular(receivedType) + '.';
        return msg;
    },

    'errmsg:expected-value-of-type-but-got': (expectedType: any, receivedType: any) =>
        'Se esperaba ' +
        typeAsNoun(expectedType) +
        ' ' +
        'pero se recibió ' +
        typeAsNoun(receivedType) +
        '.',

    'errmsg:expected-value-of-some-type-but-got': (expectedTypes: any, receivedType: any) =>
        'Se esperaba un valor de alguno de los siguientes tipos: ' +
        listOfTypes(expectedTypes) +
        '. ' +
        'Pero se recibió ' +
        typeAsNoun(receivedType) +
        '.',

    'errmsg:expected-values-to-have-compatible-types': (type1: any, type2: any) =>
        'Los tipos de las expresiones no coinciden: ' +
        'la primera es ' +
        typeAsQualifierSingular(type1) +
        ' ' +
        'y la segunda es ' +
        typeAsQualifierSingular(type2) +
        '.',

    'errmsg:switch-does-not-match':
        'El valor analizado no coincide con ninguna de las ramas del switch.',

    'errmsg:foreach-pattern-does-not-match':
        'El elemento no coincide con el patrón esperado por el foreach.',

    'errmsg:cannot-divide-by-zero': 'No se puede dividir por cero.',

    'errmsg:negative-exponent': 'El exponente de la potencia no puede ser negativo.',

    'errmsg:list-cannot-be-empty': 'La lista no puede ser vacía.',

    'errmsg:timeout': (millisecs: { toString: () => string }) =>
        'La ejecución del programa demoró más de ' + millisecs.toString() + 'ms.',

    /* Typecheck */
    'errmsg:typecheck-failed': (errorMessage: any, type1: any, type2: any) =>
        formatTypes(errorMessage, type1, type2),

    /* Board operations */
    'errmsg:cannot-move-to': (dirName: string) =>
        'No se puede mover hacia la dirección ' + dirName + ': cae afuera del tablero.',

    'errmsg:cannot-remove-stone': (dirName: string) =>
        'No se puede sacar una bolita de color ' + dirName + ': no hay bolitas de ese color.',

    /* Runtime */

    'TYPE:Integer': 'Number',
    'TYPE:String': 'String',
    'TYPE:Tuple': '',
    'TYPE:List': 'List',

    'TYPE:Event': 'Event',
    'CONS:INIT': 'INIT',
    'CONS:TIMEOUT': 'TIMEOUT',

    'TYPE:Bool': 'Bool',
    'CONS:False': 'False',
    'CONS:True': 'True',

    'TYPE:Color': 'Color',
    'CONS:Color0': 'Azul',
    'CONS:Color1': 'Negro',
    'CONS:Color2': 'Rojo',
    'CONS:Color3': 'Verde',

    'TYPE:Dir': 'Dir',
    'CONS:Dir0': 'Norte',
    'CONS:Dir1': 'Este',
    'CONS:Dir2': 'Sur',
    'CONS:Dir3': 'Oeste',

    'PRIM:TypeCheck': 'TypeCheck',
    'PRIM:BOOM': 'BOOM',
    'PRIM:boom': 'boom',

    'PRIM:PutStone': 'Poner',
    'PRIM:RemoveStone': 'Sacar',
    'PRIM:Move': 'Mover',
    'PRIM:GoToEdge': 'IrAlBorde',
    'PRIM:EmptyBoardContents': 'VaciarTablero',
    'PRIM:numStones': 'nroBolitas',
    'PRIM:anyStones': 'hayBolitas',
    'PRIM:canMove': 'puedeMover',
    'PRIM:next': 'siguiente',
    'PRIM:prev': 'previo',
    'PRIM:opposite': 'opuesto',
    'PRIM:minBool': 'minBool',
    'PRIM:maxBool': 'maxBool',
    'PRIM:minColor': 'minColor',
    'PRIM:maxColor': 'maxColor',
    'PRIM:minDir': 'minDir',
    'PRIM:maxDir': 'maxDir',

    'PRIM:isEmpty': 'esVacía',
    'PRIM:head': 'primero',
    'PRIM:tail': 'sinElPrimero',
    'PRIM:oldTail': 'resto',
    'PRIM:init': 'comienzo',
    'PRIM:last': 'último',

    /* Helpers */
    '<alternative>': (strings: any[]) =>
        'alguna de las siguientes alternativas:\n' +
        strings.map((s: string) => '  ' + s).join('\n'),
    '<position>': (
        filename: string,
        line: { toString: () => string },
        column: { toString: () => string }
    ) => filename + ':' + line.toString() + ':' + column.toString(),
    '<n>-parameters': (n: any) => pluralize(n, 'parámetro', 'parámetros'),
    '<n>-arguments': (n: any) => pluralize(n, 'argumento', 'argumentos'),
    '<n>-fields': (n: any) => pluralize(n, 'campo', 'campos'),
    '<pattern-type>'(patternType: string) {
        if (patternType === 'Event') {
            return 'evento del programa interactivo';
        } else if (patternType.substring(0, 7) === '_TUPLE_') {
            return 'tupla de ' + patternType.substring(7) + ' componentes';
        } else {
            return patternType;
        }
    }
};
