From e5065d9baac2873a4063e2327c74ae5c380d6703 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Tue, 1 Sep 2026 12:40:05 +0200 Subject: [PATCH] Fix invalid placeholders in translations (#7612) * Add logic to find invalid string placeholders in `checkForbiddenTerms.py` * Fix found invalid placeholder in Chinese translations --- .../src/main/res/values-zh/translations.xml | 2 +- tools/localazy/checkForbiddenTerms.py | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/features/poll/api/src/main/res/values-zh/translations.xml b/features/poll/api/src/main/res/values-zh/translations.xml index 15f71240d4..4cde80b945 100644 --- a/features/poll/api/src/main/res/values-zh/translations.xml +++ b/features/poll/api/src/main/res/values-zh/translations.xml @@ -1,7 +1,7 @@ - "占总投票的 %1$d%" + "占总投票的 %1$d%%" "将移除之前的选择" "此为胜出的答案" diff --git a/tools/localazy/checkForbiddenTerms.py b/tools/localazy/checkForbiddenTerms.py index 48bf7030b8..3bf893dd0b 100755 --- a/tools/localazy/checkForbiddenTerms.py +++ b/tools/localazy/checkForbiddenTerms.py @@ -42,6 +42,31 @@ forbiddenTerms = { ] } +# A complete, VALID placeholder, anchored to consume the whole token: +# %% -> literal percent (allowed) +# %s %d %f ... -> simple conversion +# %1$s %2$d ... -> positional +# %.2f %1$.2f -> optional precision +_VALID_PLACEHOLDER = re.compile( + r'%(?:\d+\$)?(?:\.\d+)?[a-zA-Z]' +) + +# Grab each candidate token: a '%' plus the run of chars that could belong to a +# specifier (digits, '$', '.', and letters). This makes '%sd' and '%1d' surface +# as single tokens instead of being partially swallowed. +_PLACEHOLDER_MATCHES = re.compile(r'%%|%[0-9$.A-Za-z]*') + +def find_invalid_placeholders(value): + """Return the list of malformed placeholder tokens found in `value`.""" + invalid = [] + for token in _PLACEHOLDER_MATCHES.findall(value): + if token == '%%': + continue # escaped percent is fine + if not _VALID_PLACEHOLDER.fullmatch(token): + invalid.append(token) + return invalid + + content = minidom.parse(file) errors = [] @@ -60,6 +85,9 @@ for elem in content.getElementsByTagName('string'): matches = re.search(term, value) if matches and name not in exceptions: errors.append('Forbidden term "' + term + '" in string: "' + name + '": ' + value) + invalid_placeholders = find_invalid_placeholders(value) + for placeholder in invalid_placeholders: + errors.append('Invalid placeholder "' + placeholder + '" in string: "' + name + '": ' + value) ### Plurals for elem in content.getElementsByTagName('plurals'): @@ -78,6 +106,9 @@ for elem in content.getElementsByTagName('plurals'): matches = re.search(term, value) if matches and name not in exceptions: errors.append('Forbidden term "' + term + '" in plural: "' + name + '": ' + value) + invalid_placeholders = find_invalid_placeholders(value) + for placeholder in invalid_placeholders: + errors.append('Invalid placeholder "' + placeholder + '" in plural: "' + name + '": ' + value) # If errors is not empty print the report if errors: