Skip to content

Commit

Permalink
🪲 Fix translation of colors (#5552)
Browse files Browse the repository at this point in the history
Fixes #5551
Colors are not properly translated and always remain in English. This PR adds colors to the translator, so that they are properly converted to the target language and back to the original.

**How to test**
- Automated tests are added, including translation tests. Note that existing level tests do not catch this error because when a keyword remains in English, it is assumed that no translation has been added yet.
- To manually test, translate to any language the following program in level 2 or up: `color red`
  • Loading branch information
boryanagoncharenko authored May 23, 2024
1 parent 2f079af commit 11b30db
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 75 deletions.
169 changes: 101 additions & 68 deletions hedy_translation.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,161 +199,194 @@ def __init__(self, input_string):
self.rules = []

def define(self, tree):
self.add_rule("_DEFINE", "define", tree)
self.add_rule_for_grammar_token("_DEFINE", "define", tree)

def defs(self, tree):
self.add_rule("_DEF", "def", tree)
self.add_rule_for_grammar_token("_DEF", "def", tree)

def call(self, tree):
self.add_rule("_CALL", "call", tree)
self.add_rule_for_grammar_token("_CALL", "call", tree)

def withs(self, tree):
self.add_rule("_WITH", "with", tree)
self.add_rule_for_grammar_token("_WITH", "with", tree)

def returns(self, tree):
self.add_rule("_RETURN", "return", tree)
self.add_rule_for_grammar_token("_RETURN", "return", tree)

def print(self, tree):
self.add_rule("_PRINT", "print", tree)
self.add_rule_for_grammar_token("_PRINT", "print", tree)

def print_empty_brackets(self, tree):
self.print(tree)

def ask(self, tree):
self.add_rule("_IS", "is", tree)
self.add_rule("_ASK", "ask", tree)
self.add_rule_for_grammar_token("_IS", "is", tree)
self.add_rule_for_grammar_token("_ASK", "ask", tree)

def echo(self, tree):
self.add_rule("_ECHO", "echo", tree)
self.add_rule_for_grammar_token("_ECHO", "echo", tree)

def color(self, tree):
self.add_rule("_COLOR", "color", tree)
self.add_rule_for_grammar_token("_COLOR", "color", tree)

def forward(self, tree):
self.add_rule("_FORWARD", "forward", tree)
self.add_rule_for_grammar_token("_FORWARD", "forward", tree)

def turn(self, tree):
self.add_rule("_TURN", "turn", tree)
self.add_rule_for_grammar_token("_TURN", "turn", tree)

def left(self, tree):
# somehow for some Arabic rules (left, right, random) the parser returns separate tokens instead of one!
token_start = tree.children[0]
token_end = tree.children[-1]
value = ''.join(tree.children)
rule = Rule("left", token_start.line, token_start.column - 1, token_end.end_column - 2, value)
self.rules.append(rule)
self.add_rule_for_grammar_rule("left", tree)

def right(self, tree):
token_start = tree.children[0]
token_end = tree.children[-1]
value = ''.join(tree.children)
rule = Rule("right", token_start.line, token_start.column - 1, token_end.end_column - 2, value)
self.rules.append(rule)
self.add_rule_for_grammar_rule("right", tree)

def black(self, tree):
self.add_rule_for_grammar_rule("black", tree)

def blue(self, tree):
self.add_rule_for_grammar_rule("blue", tree)

def brown(self, tree):
self.add_rule_for_grammar_rule("brown", tree)

def gray(self, tree):
self.add_rule_for_grammar_rule("gray", tree)

def green(self, tree):
self.add_rule_for_grammar_rule("green", tree)

def orange(self, tree):
self.add_rule_for_grammar_rule("orange", tree)

def pink(self, tree):
self.add_rule_for_grammar_rule("pink", tree)

def yellow(self, tree):
self.add_rule_for_grammar_rule("yellow", tree)

def purple(self, tree):
self.add_rule_for_grammar_rule("purple", tree)

def white(self, tree):
self.add_rule_for_grammar_rule("white", tree)

def red(self, tree):
self.add_rule_for_grammar_rule("red", tree)

def clear(self, tree):
self.add_rule_for_grammar_rule("clear", tree)

def assign_list(self, tree):
self.add_rule("_IS", "is", tree)
self.add_rule_for_grammar_token("_IS", "is", tree)
commas = self.get_keyword_tokens("_COMMA", tree)
for comma in commas:
rule = Rule("comma", comma.line, comma.column - 1, comma.end_column - 2, comma.value)
self.rules.append(rule)

def assign(self, tree):
self.add_rule("_IS", "is", tree)
self.add_rule_for_grammar_token("_IS", "is", tree)

def sleep(self, tree):
self.add_rule("_SLEEP", "sleep", tree)
self.add_rule_for_grammar_token("_SLEEP", "sleep", tree)

def add(self, tree):
self.add_rule("_ADD_LIST", "add", tree)
self.add_rule("_TO_LIST", "to_list", tree)
self.add_rule_for_grammar_token("_ADD_LIST", "add", tree)
self.add_rule_for_grammar_token("_TO_LIST", "to_list", tree)

def remove(self, tree):
self.add_rule("_REMOVE", "remove", tree)
self.add_rule("_FROM", "from", tree)
self.add_rule_for_grammar_token("_REMOVE", "remove", tree)
self.add_rule_for_grammar_token("_FROM", "from", tree)

def random(self, tree):
# somehow for Arabic tokens, we parse into separate tokens instead of one!
token_start = tree.children[0]
token_end = tree.children[-1]
value = ''.join(tree.children)
rule = Rule("random", token_start.line, token_start.column - 1, token_end.end_column - 2, value)
self.rules.append(rule)
self.add_rule_for_grammar_rule("random", tree)

def error_ask_dep_2(self, tree):
self.add_rule("_ASK", "ask", tree)
self.add_rule_for_grammar_token("_ASK", "ask", tree)

def error_echo_dep_2(self, tree):
self.add_rule("_ECHO", "echo", tree)
self.add_rule_for_grammar_token("_ECHO", "echo", tree)

def ifs(self, tree):
self.add_rule("_IF", "if", tree)
self.add_rule_for_grammar_token("_IF", "if", tree)

def ifelse(self, tree):
self.add_rule("_IF", "if", tree)
self.add_rule("_ELSE", "else", tree)
self.add_rule_for_grammar_token("_IF", "if", tree)
self.add_rule_for_grammar_token("_ELSE", "else", tree)

def elifs(self, tree):
self.add_rule("_ELIF", "elif", tree)
self.add_rule_for_grammar_token("_ELIF", "elif", tree)

def elses(self, tree):
self.add_rule("_ELSE", "else", tree)
self.add_rule_for_grammar_token("_ELSE", "else", tree)

def condition_spaces(self, tree):
self.add_rule("_IS", "is", tree)
self.add_rule_for_grammar_token("_IS", "is", tree)

def equality_check_is(self, tree):
self.equality_check(tree)

def equality_check(self, tree):
self.add_rule("_IS", "is", tree)
self.add_rule("_EQUALS", "=", tree)
self.add_rule("_DOUBLE_EQUALS", "==", tree)
self.add_rule_for_grammar_token("_IS", "is", tree)
self.add_rule_for_grammar_token("_EQUALS", "=", tree)
self.add_rule_for_grammar_token("_DOUBLE_EQUALS", "==", tree)

def in_list_check(self, tree):
self.add_rule("_IN", "in", tree)
self.add_rule_for_grammar_token("_IN", "in", tree)

def list_access(self, tree):
self.add_rule("_AT", "at", tree)
self.add_rule_for_grammar_token("_AT", "at", tree)

def list_access_var(self, tree):
self.add_rule("_IS", "is", tree)
self.add_rule("_AT", "at", tree)
self.add_rule_for_grammar_token("_IS", "is", tree)
self.add_rule_for_grammar_token("_AT", "at", tree)

def repeat(self, tree):
self.add_rule("_REPEAT", "repeat", tree)
self.add_rule("_TIMES", "times", tree)
self.add_rule_for_grammar_token("_REPEAT", "repeat", tree)
self.add_rule_for_grammar_token("_TIMES", "times", tree)

def for_list(self, tree):
self.add_rule("_FOR", "for", tree)
self.add_rule("_IN", "in", tree)
self.add_rule_for_grammar_token("_FOR", "for", tree)
self.add_rule_for_grammar_token("_IN", "in", tree)

def for_loop(self, tree):
self.add_rule("_FOR", "for", tree)
self.add_rule("_IN", "in", tree)
self.add_rule("_RANGE", "range", tree)
self.add_rule("_TO", "to", tree)
self.add_rule_for_grammar_token("_FOR", "for", tree)
self.add_rule_for_grammar_token("_IN", "in", tree)
self.add_rule_for_grammar_token("_RANGE", "range", tree)
self.add_rule_for_grammar_token("_TO", "to", tree)

def while_loop(self, tree):
self.add_rule("_WHILE", "while", tree)
self.add_rule_for_grammar_token("_WHILE", "while", tree)

def and_condition(self, tree):
self.add_rule("_AND", "and", tree)
self.add_rule_for_grammar_token("_AND", "and", tree)

def or_condition(self, tree):
self.add_rule("_OR", "or", tree)
self.add_rule_for_grammar_token("_OR", "or", tree)

def input(self, tree):
self.add_rule("_IS", "is", tree)
self.add_rule("_INPUT", "input", tree)
self.add_rule_for_grammar_token("_IS", "is", tree)
self.add_rule_for_grammar_token("_INPUT", "input", tree)

def input_empty_brackets(self, tree):
self.add_rule("_IS", "is", tree)
self.add_rule("_INPUT", "input", tree)
self.add_rule_for_grammar_token("_IS", "is", tree)
self.add_rule_for_grammar_token("_INPUT", "input", tree)

def pressed(self, tree):
self.add_rule("_PRESSED", "pressed", tree)
self.add_rule_for_grammar_token("_PRESSED", "pressed", tree)

def add_rule_for_grammar_rule(self, rule_name, tree):
"""Creates a translation rule for a rule defined in the lark grammar which
could have multiple children tokens, e.g. left, random, red"""
# somehow for some Arabic rules (left, right, random) the parser returns separate tokens instead of one!
token_start = tree.children[0]
token_end = tree.children[-1]
value = ''.join(tree.children)
rule = Rule(rule_name, token_start.line, token_start.column - 1, token_end.end_column - 2, value)
self.rules.append(rule)

def add_rule(self, token_name, token_keyword, tree):
def add_rule_for_grammar_token(self, token_name, token_keyword, tree):
"""Creates a translation rule for a token defined in the lark grammar, e.g. _DEFINE, _FOR, _TURN"""
token = self.get_keyword_token(token_name, tree)
if token:
rule = Rule(
Expand Down
1 change: 0 additions & 1 deletion tests/test_level/test_level_01.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,6 @@ def test_turn_left_nl(self):
)

def test_turn_ar(self):
# doesn't translate, I don't know why!!
code = "استدر يسار"
expected = "t.left(90)"

Expand Down
20 changes: 16 additions & 4 deletions tests/test_level/test_level_02.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,15 +484,27 @@ def test_turn_right_number_gives_type_error(self):
)

# color tests
def test_color_red(self):
code = "color red"
expected = HedyTester.turtle_color_command_transpiled('red')

@parameterized.expand(hedy.english_colors)
def test_all_colors(self, color):
code = f'color {color}'
expected = HedyTester.turtle_color_command_transpiled(color)

self.multi_level_tester(
code=code,
expected=expected,
extra_check_function=self.is_turtle(),
max_level=10
)

def test_color_red_ar(self):
code = 'لون احمر'
expected = HedyTester.turtle_color_command_transpiled('red', lang='ar')

self.multi_level_tester(
code=code,
expected=expected,
extra_check_function=self.is_turtle(),
lang='ar'
)

def test_color_with_var(self):
Expand Down
25 changes: 23 additions & 2 deletions tests/test_translation_level/test_translation_level_02.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def test_ask_print_all_lang(self, ask_keyword, is_keyword, print_keyword, lang):

self.assertEqual(expected, result)

def no_argument_ask_english(self):
def test_no_argument_ask_english(self):
code = "ask"

result = hedy_translation.translate_keywords(
Expand All @@ -167,11 +167,32 @@ def no_argument_ask_english(self):

self.assertEqual(expected, result)

def no_argument_ask_dutch(self):
def test_no_argument_ask_dutch(self):
code = "vraag"

result = hedy_translation.translate_keywords(
code, "nl", "en", self.level)
expected = "ask"

self.assertEqual(expected, result)

@parameterized.expand([
('black', 'اسود'),
('blue', 'ازرق'),
('brown', 'بني'),
('gray', 'رمادي'),
('green', 'اخضر'),
('orange', 'برتقالي'),
('pink', 'زهري'),
('purple', 'بنفسجي'),
('red', 'احمر'),
('white', 'ابيض'),
('yellow', 'اصفر')
])
def test_color_english_arabic(self, en, ar):
code = f"color {en}"

result = hedy_translation.translate_keywords(code, from_lang="en", to_lang="ar", level=self.level)
expected = f'لون {ar}'

self.assertEqual(expected, result)

0 comments on commit 11b30db

Please sign in to comment.