diff --git a/babel/numbers.py b/babel/numbers.py index b782521e5..e30ba5a86 100644 --- a/babel/numbers.py +++ b/babel/numbers.py @@ -1397,14 +1397,19 @@ def __repr__(self) -> str: def compute_scale(self) -> Literal[0, 2, 3]: """Return the scaling factor to apply to the number before rendering. - Auto-set to a factor of 2 or 3 if presence of a ``%`` or ``‰`` sign is + Auto-set to a factor of 2 or 3 if an unquoted ``%`` or ``‰`` sign is detected in the prefix or suffix of the pattern. Default is to not mess with the scale at all and keep it to 0. """ + # Ignore complete quoted spans, as in the affix rendering in apply(). + affixes = ''.join( + re.sub(r"'[^']*'", '', affix) + for affix in self.prefix + self.suffix + ) scale = 0 - if '%' in ''.join(self.prefix + self.suffix): + if '%' in affixes: scale = 2 - elif '‰' in ''.join(self.prefix + self.suffix): + elif '‰' in affixes: scale = 3 return scale diff --git a/tests/test_numbers.py b/tests/test_numbers.py index d20a18315..2e27f88f7 100644 --- a/tests/test_numbers.py +++ b/tests/test_numbers.py @@ -660,6 +660,62 @@ def test_single_quotes_in_pattern(): assert numbers.format_decimal(12, "'#'0 o''clock", locale='en') == "#12 o'clock" +@pytest.mark.parametrize('formatter', [numbers.format_decimal, numbers.format_percent]) +@pytest.mark.parametrize( + ('pattern', 'value', 'expected'), + [ + ("0.0'%'", '12.3', '12.3%'), + ("0.0'%'", '-12.3', '-12.3%'), + ("'%'0.0", '12.3', '%12.3'), + ("'%'0.0", '-12.3', '-%12.3'), + ("0.0'‰'", '12.3', '12.3‰'), + ("0.0'‰'", '-12.3', '-12.3‰'), + ("'‰'0.0", '12.3', '‰12.3'), + ("'‰'0.0", '-12.3', '-‰12.3'), + ("0.0''%", '12.3', "1230.0'%"), + ("0.0''‰", '12.3', "12300.0'‰"), + ("0.0'%'‰", '12.3', '12300.0%‰'), + ("‰0.0'%'", '12.3', '‰12300.0%'), + ("0.0'‰'%", '12.3', '1230.0‰%'), + ("0.0'%';(0.0'%')", '-12.3', '(12.3%)'), + ("0.0'‰';(0.0'‰')", '-12.3', '(12.3‰)'), + ('0.0%;(0.0%)', '-12.3', '(1230.0%)'), + ('0.0‰;(0.0‰)', '-12.3', '(12300.0‰)'), + ("0.0E0'%'", '12.3', '1.2E1%'), + ("@@'%'", '12.3', '12%'), + ("0.0'%", '12.3', "1230.0'%"), + ("0.0'‰", '12.3', "12300.0'‰"), + ("0.0'%;0.0‰", '-12.3', '1230.0‰'), + ], +) +def test_quoted_scale_symbols(formatter, pattern, value, expected): + assert formatter(decimal.Decimal(value), pattern, locale='en_US') == expected + + +@pytest.mark.parametrize( + ('pattern', 'scale'), + [ + ("0'%'", 0), + ("0'‰'", 0), + ("0'%'‰", 3), + ('0%', 2), + ('0‰', 3), + ('0%‰', 2), + ('0‰%', 2), + ('0;(0%)', 2), + ('0;(0‰)', 3), + ("0;(0'%')", 0), + ("0;(0'‰')", 0), + ("0' don''t scale %'", 0), + ("0' don''t scale ‰'", 0), + ("0'%;0‰", 2), + ("0'‰;0%", 2), + ], +) +def test_number_pattern_scale(pattern, scale): + assert numbers.parse_pattern(pattern).scale == scale + + def test_format_currency_with_none_locale_with_default(monkeypatch): """Test that the default locale is used when locale is None.""" monkeypatch.setattr(numbers, "LC_MONETARY", "fi_FI")