From 11b30db3beab79ebe2bfee14fc0554c188a4e81e Mon Sep 17 00:00:00 2001 From: boryanagoncharenko <3010723+boryanagoncharenko@users.noreply.github.com> Date: Thu, 23 May 2024 22:59:14 +0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=AA=B2=20Fix=20translation=20of=20colors?= =?UTF-8?q?=20(#5552)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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` --- hedy_translation.py | 169 +++++++++++------- tests/test_level/test_level_01.py | 1 - tests/test_level/test_level_02.py | 20 ++- .../test_translation_level_02.py | 25 ++- 4 files changed, 140 insertions(+), 75 deletions(-) diff --git a/hedy_translation.py b/hedy_translation.py index f446feb2be8..94a6f8a60ec 100644 --- a/hedy_translation.py +++ b/hedy_translation.py @@ -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( diff --git a/tests/test_level/test_level_01.py b/tests/test_level/test_level_01.py index 04f5d439410..e8cf087601a 100644 --- a/tests/test_level/test_level_01.py +++ b/tests/test_level/test_level_01.py @@ -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)" diff --git a/tests/test_level/test_level_02.py b/tests/test_level/test_level_02.py index ee052de0187..4c939736959 100644 --- a/tests/test_level/test_level_02.py +++ b/tests/test_level/test_level_02.py @@ -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): diff --git a/tests/test_translation_level/test_translation_level_02.py b/tests/test_translation_level/test_translation_level_02.py index 0747d08fdce..280b6c82249 100644 --- a/tests/test_translation_level/test_translation_level_02.py +++ b/tests/test_translation_level/test_translation_level_02.py @@ -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( @@ -167,7 +167,7 @@ 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( @@ -175,3 +175,24 @@ def no_argument_ask_dutch(self): 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)