diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF027_1.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF027_1.py index 3684f77a39..964147300b 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF027_1.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF027_1.py @@ -25,6 +25,10 @@ def negative_cases(): json3 = "{ 'positive': 'false' }" alternative_formatter("{a}", a=5) formatted = "{a}".fmt(a=7) + partial = "partial sentence" + a = _("formatting of {partial} in a translation string is bad practice") + _("formatting of {partial} in a translation string is bad practice") + print(_("formatting of {partial} in a translation string is bad practice")) print(do_nothing("{a}".format(a=3))) print(do_nothing(alternative_formatter("{a}", a=5))) print(format(do_nothing("{a}"), a=5)) diff --git a/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs b/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs index 3e11577e8b..583a519473 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/missing_fstring_syntax.rs @@ -70,6 +70,11 @@ pub(crate) fn missing_fstring_syntax( } } + // We also want to avoid expressions that are intended to be translated. + if semantic.current_expressions().any(is_gettext) { + return; + } + if should_be_fstring(literal, locator, semantic) { let diagnostic = Diagnostic::new(MissingFStringSyntax, literal.range()) .with_fix(fix_fstring_syntax(literal.range())); @@ -77,6 +82,26 @@ pub(crate) fn missing_fstring_syntax( } } +/// Returns `true` if an expression appears to be a `gettext` call. +/// +/// We want to avoid statement expressions and assignments related to aliases +/// of the gettext API. +/// +/// See for details. When one +/// uses `_` to mark a string for translation, the tools look for these markers +/// and replace the original string with its translated counterpart. If the +/// string contains variable placeholders or formatting, it can complicate the +/// translation process, lead to errors or incorrect translations. +fn is_gettext(expr: &ast::Expr) -> bool { + let ast::Expr::Call(ast::ExprCall { func, .. }) = expr else { + return false; + }; + let ast::Expr::Name(ast::ExprName { id, .. }) = func.as_ref() else { + return false; + }; + matches!(id.as_str(), "_" | "gettext" | "ngettext") +} + /// Returns `true` if `literal` is likely an f-string with a missing `f` prefix. /// See [`MissingFStringSyntax`] for the validation criteria. fn should_be_fstring(