diff --git a/docs/user/checks.rst b/docs/user/checks.rst index 4cb47339989c..7ee4346a7c2f 100644 --- a/docs/user/checks.rst +++ b/docs/user/checks.rst @@ -1219,6 +1219,24 @@ Coptic). `Question mark on Wikipedia `_ +.. _check-end-interrobang: + +Mismatched interrobang mark +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:Summary: Source and translation do not both end with a interrobang mark +:Scope: translated strings +:Check class: ``weblate.checks.chars.EndInterrobangCheck`` +:Check identifier: ``end_Interrobang`` +:Flag to ignore: ``ignore-end-Interrobang`` + +Checks that interrobang marks are replicated between both source and translation. +It allows the swap between "!?" and "?!". + +.. seealso:: + + `Interrobang mark on Wikipedia `_ + .. _check-end-semicolon: Mismatched semicolon diff --git a/weblate/checks/chars.py b/weblate/checks/chars.py index 356ef2d52127..75e03ca2974f 100644 --- a/weblate/checks/chars.py +++ b/weblate/checks/chars.py @@ -231,6 +231,7 @@ class EndQuestionCheck(TargetCheck): "Source and translation do not both end with a question mark" ) question_el = ("?", ";", ";") + interrobangs = ("?!", "!?", "?!", "!?", "⁈", "⁉") def _check_hy(self, source, target): if source[-1] == "?": @@ -248,6 +249,8 @@ def _check_my(self, source, target): def check_single(self, source, target, unit): if not source or not target: return False + if source.endswith(self.interrobangs) or target.endswith(self.interrobangs): + return False if unit.translation.language.is_base(("jbo",)): return False if unit.translation.language.is_base(("hy",)): @@ -270,10 +273,13 @@ class EndExclamationCheck(TargetCheck): description = gettext_lazy( "Source and translation do not both end with an exclamation mark" ) + interrobangs = ("?!", "!?", "?!", "!?", "⁈", "⁉") def check_single(self, source, target, unit): if not source or not target: return False + if source.endswith(self.interrobangs) or target.endswith(self.interrobangs): + return False if ( unit.translation.language.is_base(("eu",)) and source[-1] == "!" @@ -290,6 +296,27 @@ def check_single(self, source, target, unit): return self.check_chars(source, target, -1, ("!", "!", "՜", "᥄", "႟", "߹")) +class EndInterrobangCheck(TargetCheck): + """Check for final interrobang expression.""" + + check_id = "end_Interrobang" + name = gettext_lazy("Mismatched interrobang") + description = gettext_lazy( + "Source and translation do not both end with an interrobang expression" + ) + + def check_single(self, source, target, unit): + if not source or not target: + return False + + interrobangs = ("!?", "?!", "?!", "!?") + + if (source.endswith(interrobangs)) != (target.endswith(interrobangs)): + return True + + return bool(self.check_chars(source, target, -1, ("⁈", "⁉"))) + + class EndEllipsisCheck(TargetCheck): """Check for ellipsis at the end of string.""" diff --git a/weblate/checks/models.py b/weblate/checks/models.py index 1758c3773604..e1b50efa17ce 100644 --- a/weblate/checks/models.py +++ b/weblate/checks/models.py @@ -39,6 +39,7 @@ class WeblateChecksConf(AppConf): "weblate.checks.chars.EndColonCheck", "weblate.checks.chars.EndQuestionCheck", "weblate.checks.chars.EndExclamationCheck", + "weblate.checks.chars.EndInterrobangCheck", "weblate.checks.chars.EndEllipsisCheck", "weblate.checks.chars.EndSemicolonCheck", "weblate.checks.chars.MaxLengthCheck", diff --git a/weblate/checks/tests/test_chars_checks.py b/weblate/checks/tests/test_chars_checks.py index d519dfbaf0dd..76c2d0d0e182 100644 --- a/weblate/checks/tests/test_chars_checks.py +++ b/weblate/checks/tests/test_chars_checks.py @@ -13,6 +13,7 @@ EndColonCheck, EndEllipsisCheck, EndExclamationCheck, + EndInterrobangCheck, EndNewlineCheck, EndQuestionCheck, EndSemicolonCheck, @@ -198,6 +199,14 @@ def test_my(self) -> None: self.do_test(False, ("Text?", "ပုံဖျက်မလား။", ""), "my") self.do_test(True, ("Te xt", "ပုံဖျက်မလား။", ""), "my") + def test_interrobang(self) -> None: + self.do_test(False, ("string!?", "string?", "")) + self.do_test(False, ("string?", "string?!", "")) + self.do_test(False, ("string⁈", "string?", "")) + self.do_test(False, ("string?", "string⁉", "")) + self.do_test(False, ("string?!", "string?", "")) + self.do_test(False, ("string?", "string!?", "")) + class EndExclamationCheckTest(CheckTestCase): check = EndExclamationCheck() @@ -216,6 +225,38 @@ def test_hy(self) -> None: def test_eu(self) -> None: self.do_test(False, ("Text!", "¡Texte!", ""), "eu") + def test_interrobang(self) -> None: + self.do_test(False, ("string!?", "string!", "")) + self.do_test(False, ("string!", "string?!", "")) + self.do_test(False, ("string⁈", "string!", "")) + self.do_test(False, ("string!", "string⁉", "")) + self.do_test(False, ("string?!", "string!", "")) + self.do_test(False, ("string!", "string!?", "")) + + +class EndInterrobangCheckTest(CheckTestCase): + check = EndInterrobangCheck() + + def setUp(self) -> None: + super().setUp() + self.test_good_matching = ("string!?", "string?!", "") + self.test_failure_1 = ("string!?", "string?", "") + self.test_failure_2 = ("string!?", "string!", "") + self.test_failure_3 = ("string!", "string!?", "") + + def test_translate(self) -> None: + self.do_test(False, ("string!?", "string!?", "")) + self.do_test(False, ("string⁉", "string⁈", "")) + self.do_test(False, ("string⁉", "string⁉", "")) + self.do_test(False, ("string!?", "string!?", "")) + self.do_test(False, ("string!?", "string?!", "")) + self.do_test(False, ("string?!", "string?!", "")) + self.do_test(False, ("string!?", "string!?", "")) + self.do_test(True, ("string?", "string?!", "")) + self.do_test(True, ("string⁉", "string!?", "")) + self.do_test(True, ("string?!", "string⁈", "")) + self.do_test(True, ("string?!", "string⁈", "")) + class EndEllipsisCheckTest(CheckTestCase): check = EndEllipsisCheck() diff --git a/weblate/settings_docker.py b/weblate/settings_docker.py index f2db057398b4..3b958bbaaf64 100644 --- a/weblate/settings_docker.py +++ b/weblate/settings_docker.py @@ -980,6 +980,7 @@ "weblate.checks.chars.EndColonCheck", "weblate.checks.chars.EndQuestionCheck", "weblate.checks.chars.EndExclamationCheck", + "weblate.checks.chars.EndInterrobangCheck", "weblate.checks.chars.EndEllipsisCheck", "weblate.checks.chars.EndSemicolonCheck", "weblate.checks.chars.MaxLengthCheck", diff --git a/weblate/settings_example.py b/weblate/settings_example.py index 14fb0979e633..1e839a043dd0 100644 --- a/weblate/settings_example.py +++ b/weblate/settings_example.py @@ -633,6 +633,7 @@ # "weblate.checks.chars.EndColonCheck", # "weblate.checks.chars.EndQuestionCheck", # "weblate.checks.chars.EndExclamationCheck", +# "weblate.checks.chars.EndInterrobangCheck", # "weblate.checks.chars.EndEllipsisCheck", # "weblate.checks.chars.EndSemicolonCheck", # "weblate.checks.chars.MaxLengthCheck",