diff --git a/odoo_module_migrate/migration_scripts/migrate_130_allways.py b/odoo_module_migrate/migration_scripts/migrate_130_allways.py new file mode 100644 index 00000000..99294ac9 --- /dev/null +++ b/odoo_module_migrate/migration_scripts/migrate_130_allways.py @@ -0,0 +1,83 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import re +from odoo_module_migrate.base_migration_script import BaseMigrationScript + + +def multi_value_translation_replacement_function(match, single_quote=True): + format_string = match.group(1) + dictionary_entries = match.group(2) + + formatted_entries = [] + for entry in dictionary_entries.split(","): + if ":" in entry: + [key, value] = entry.split(":") + formatted_entries.append( + "{}={}".format(key.strip().strip("'").strip('"'), value.strip()) + ) + + formatted_entries = ", ".join(formatted_entries) + + if single_quote: + return f"_('{format_string}', {formatted_entries})" + return f'_("{format_string}", {formatted_entries})' + + +def format_parenthesis(match): + format_string = match.group(1) + dictionary_entries = match.group(2) + + if dictionary_entries.endswith(","): + dictionary_entries = dictionary_entries[:-1] + + return f"_({format_string}, {dictionary_entries})" + + +def format_replacement_function(match, single_quote=True): + format_string = re.sub(r"\{\d*\}", "%s", match.group(1)) + format_string = re.sub(r"{(\w+)}", r"%(\1)s", format_string) + arguments = " ".join(match.group(2).split()) + + if arguments.endswith(","): + arguments = arguments[:-1] + + if single_quote: + return f"_('{format_string}', {arguments})" + return f'_("{format_string}", {arguments})' + + +def replace_translation_function( + logger, module_path, module_name, manifest_path, migration_steps, tools +): + files_to_process = tools.get_files(module_path, (".py",)) + + replaces = { + r'_\(\s*"([^"]+)"\s*\)\s*%\s*\{([^}]+)\}': lambda match: multi_value_translation_replacement_function( + match, single_quote=False + ), + r"_\(\s*'([^']+)'\s*\)\s*%\s*\{([^}]+)\}": lambda match: multi_value_translation_replacement_function( + match, single_quote=True + ), + r'_\(\s*(["\'].*?%[ds].*?["\'])\s*\)\s*%\s*\(\s*(.+)\s*\)': format_parenthesis, + r'_\(\s*(["\'].*?%[ds].*?["\'])\s*\)\s*?%\s*?([^\s]+)': r"_(\1, \2)", + r'_\(\s*"([^"]*)"\s*\)\.format\(\s*(\s*[^)]+)\)': lambda match: format_replacement_function( + match, single_quote=False + ), + r"_\(\s*'([^']*)'\s*\)\.format\(\s*(\s*[^)]+)\)": lambda match: format_replacement_function( + match, single_quote=True + ), + } + + for file in files_to_process: + try: + tools._replace_in_file( + file, + replaces, + log_message=f"""Improve _() function: {file}""", + ) + except Exception as e: + logger.error(f"Error processing file {file}: {str(e)}") + + +class MigrationScript(BaseMigrationScript): + + _GLOBAL_FUNCTIONS = [replace_translation_function] diff --git a/odoo_module_migrate/migration_scripts/text_warnings/migrate_allways/unnamed_placeholders_translated_string.yaml b/odoo_module_migrate/migration_scripts/text_warnings/migrate_allways/unnamed_placeholders_translated_string.yaml new file mode 100644 index 00000000..08b82628 --- /dev/null +++ b/odoo_module_migrate/migration_scripts/text_warnings/migrate_allways/unnamed_placeholders_translated_string.yaml @@ -0,0 +1,2 @@ +.py: + _\(\s*["\'].*?%s.*?["\']: "[Warning] In some languages the order of the placeholders may have to be modified, which is impossible if they are unnamed. We can use named placedholders to avoid this" diff --git a/tests/data_result/module_130_140/__init__.py b/tests/data_result/module_130_140/__init__.py index e69de29b..0650744f 100644 --- a/tests/data_result/module_130_140/__init__.py +++ b/tests/data_result/module_130_140/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/tests/data_result/module_130_140/models/__init__.py b/tests/data_result/module_130_140/models/__init__.py new file mode 100644 index 00000000..91fed54d --- /dev/null +++ b/tests/data_result/module_130_140/models/__init__.py @@ -0,0 +1 @@ +from . import res_partner diff --git a/tests/data_result/module_130_140/models/res_partner.py b/tests/data_result/module_130_140/models/res_partner.py new file mode 100644 index 00000000..791d96fa --- /dev/null +++ b/tests/data_result/module_130_140/models/res_partner.py @@ -0,0 +1,58 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, models +from odoo.exceptions import ValidationError + + +class ResPartner(models.Model): + _inherit = "res.partner" + + def test_improve_translation(self): + return _("It's %s", 2024) + + def test_improve_transalation_with_line_break(self): + raise ValidationError( + _("The '%s' is empty or 0. It should have a non-null value.", "Price") + ) + + def test_improve_translation_with_parenthesis(self): + return _("It's %s", 2024) + + def test_improve_translation_with_single_quote(self): + return _('It is %s', 2024) + + def test_improve_translation_with_parenthesis_and_line_break(self): + raise ValidationError( + _("%s are only valid until %s", "User", 2024) + ) + + def test_improve_translation_with_brackets(self): + return _("User %(name)s has %(items)s items", name="Dev", items=5) + + def test_improve_translation_with_single_quote(self): + return _('User %(name)s has %(items)s items', name='Dev', items=5) + + def test_improve_translation_with_brackets_and_line_break(self): + return _("User %(name)s has %(items)s items", name="Dev", items=5) + + def test_improve_translation_with_format(self): + return _("It's %s", 2024) + + def test_improve_translation_with_format_and_single_quote(self): + return _('It is %s', 2024) + + def test_improve_translation_with_format_and_line_break(self): + return _("It's %s", 2024) + + def test_improve_translation_with_format_has_end_comma(self): + return _("It's %s", 2024) + + def test_improve_translation_with_format_multi_params(self): + return _("User %(name)s has %(items)s items", name="Dev", items=5) + + def test_improve_translation_with_format_multi_params_and_line_break(self): + return _("User %(name)s has %(items)s items", name="Dev", items=5) + + def test_improve_translation_with_format_multi_params_has_end_comma(self): + return _('User %(name)s has "acb" %(items)s items', name="Dev", items=5) + \ No newline at end of file diff --git a/tests/data_template/module_130/__init__.py b/tests/data_template/module_130/__init__.py index e69de29b..0650744f 100644 --- a/tests/data_template/module_130/__init__.py +++ b/tests/data_template/module_130/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/tests/data_template/module_130/models/__init__.py b/tests/data_template/module_130/models/__init__.py new file mode 100644 index 00000000..91fed54d --- /dev/null +++ b/tests/data_template/module_130/models/__init__.py @@ -0,0 +1 @@ +from . import res_partner diff --git a/tests/data_template/module_130/models/res_partner.py b/tests/data_template/module_130/models/res_partner.py new file mode 100644 index 00000000..aa0c41bf --- /dev/null +++ b/tests/data_template/module_130/models/res_partner.py @@ -0,0 +1,75 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, models +from odoo.exceptions import ValidationError + + +class ResPartner(models.Model): + _inherit = "res.partner" + + def test_improve_translation(self): + return _("It's %s") % 2024 + + def test_improve_transalation_with_line_break(self): + raise ValidationError( + _("The '%s' is empty or 0. It should have a non-null value.") + % "Price" + ) + + def test_improve_translation_with_parenthesis(self): + return _("It's %s") % (2024) + + def test_improve_translation_with_single_quote(self): + return _('It is %s') % (2024) + + def test_improve_translation_with_parenthesis_and_line_break(self): + raise ValidationError( + _("%s are only valid until %s") % ( + "User", 2024, + ) + ) + + def test_improve_translation_with_brackets(self): + return _("User %(name)s has %(items)s items") % {"name":"Dev", "items": 5} + + def test_improve_translation_with_single_quote(self): + return _('User %(name)s has %(items)s items') % {'name':'Dev', 'items': 5} + + def test_improve_translation_with_brackets_and_line_break(self): + return _( + "User %(name)s has %(items)s items" + ) % { + "name": "Dev", + "items": 5, + } + + def test_improve_translation_with_format(self): + return _("It's {}").format(2024) + + def test_improve_translation_with_format_and_single_quote(self): + return _('It is {}').format(2024) + + def test_improve_translation_with_format_and_line_break(self): + return _( + "It's {}" + ).format( + 2024 + ) + + def test_improve_translation_with_format_has_end_comma(self): + return _("It's {}").format(2024,) + + def test_improve_translation_with_format_multi_params(self): + return _("User {name} has {items} items").format(name="Dev", items=5) + + def test_improve_translation_with_format_multi_params_and_line_break(self): + return _( + "User {name} has {items} items" + ).format( + name="Dev", + items=5 + ) + + def test_improve_translation_with_format_multi_params_has_end_comma(self): + return _('User {name} has "acb" {items} items').format(name="Dev", items=5,) + \ No newline at end of file