mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
Strings moved from res/values/ into composeResources/values/ kept Android's escaping, which Compose does not interpret the same way, so the login screen rendered `Don\'t have a Nostr account?` with a literal backslash and the terms line showed stray quotes. Compose 1.11.1 handleSpecialCharacters resolves only \uXXXX, \n and \t (and collapses \\). It leaves \' \" \? \@ alone, and renders Android's quote-wrapping -- used to preserve leading/trailing spaces, e.g. " Following" -- literally. Convert those four escapes and unwrap the quotes, leaving \n, \t, \uXXXX and \\ untouched so Compose still resolves them. 3,717 entries across 56 locale files; translations were hit far harder than English (Uzbek 964, French ~340 per variant, Turkish ~208) because those languages use apostrophes heavily. migrate.py now applies the same conversion as it moves each element, so the next wave cannot reintroduce this; fix_escapes.py repairs what is already migrated and is idempotent. Verified on a Pixel 9 emulator: "Event is loading or can't be found in your relay list" now renders with a real apostrophe, and no visible text node contains a literal backslash escape. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1CzYQvWyHfipSW7x3j4Yo
79 lines
2.5 KiB
Python
Executable File
79 lines
2.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Convert Android string-resource escaping to what Compose resources understand.
|
|
|
|
Android's aapt and Compose Multiplatform do NOT share escaping rules. Moving a
|
|
strings.xml from `res/values/` to `composeResources/values/` verbatim therefore
|
|
renders some strings wrong -- the login screen showed `Don\\'t have a Nostr account?`
|
|
with a literal backslash.
|
|
|
|
Compose 1.11.1 `handleSpecialCharacters` (compose-gradle-plugin) resolves ONLY:
|
|
|
|
\\uXXXX, \\n, \\t and collapses \\\\ -> \\
|
|
|
|
It does not handle these Android conventions, so this script applies them itself:
|
|
|
|
\\' -> ' \\" -> " \\? -> ? \\@ -> @
|
|
"…" -> … (Android wraps a value in quotes to preserve leading/trailing
|
|
spaces; Compose would render the quotes literally)
|
|
|
|
\\n, \\t, \\uXXXX and \\\\ are deliberately left alone -- Compose already resolves
|
|
them, and converting them here would double-process.
|
|
|
|
Usage: fix_escapes.py <file-or-dir> [...] (idempotent; safe to re-run)
|
|
"""
|
|
import re
|
|
import sys
|
|
import os
|
|
|
|
STRING_EL = re.compile(r"(<(?:string|item)\b[^>]*>)(.*?)(</(?:string|item)>)", re.S)
|
|
# a backslash escape NOT itself preceded by a backslash
|
|
ANDROID_ONLY = re.compile(r"(?<!\\)\\(['\"?@])")
|
|
|
|
|
|
def fix_text(text: str) -> str:
|
|
# Android quote-wrapping: preserve the inner value, drop the delimiters.
|
|
if len(text) >= 2 and text.startswith('"') and text.endswith('"'):
|
|
text = text[1:-1]
|
|
return ANDROID_ONLY.sub(r"\1", text)
|
|
|
|
|
|
def fix_file(path: str) -> int:
|
|
src = open(path, encoding="utf-8").read()
|
|
changed = 0
|
|
|
|
def repl(m):
|
|
nonlocal changed
|
|
fixed = fix_text(m.group(2))
|
|
if fixed != m.group(2):
|
|
changed += 1
|
|
return m.group(1) + fixed + m.group(3)
|
|
|
|
out = STRING_EL.sub(repl, src)
|
|
if changed:
|
|
open(path, "w", encoding="utf-8").write(out)
|
|
return changed
|
|
|
|
|
|
def main(paths):
|
|
total_files = total_entries = 0
|
|
for p in paths:
|
|
files = []
|
|
if os.path.isdir(p):
|
|
for root, _, names in os.walk(p):
|
|
files += [os.path.join(root, n) for n in names if n.endswith(".xml")]
|
|
else:
|
|
files = [p]
|
|
for f in sorted(files):
|
|
n = fix_file(f)
|
|
if n:
|
|
total_files += 1
|
|
total_entries += n
|
|
print(f" {f}: {n} entries")
|
|
print(f"\nfixed {total_entries} entries across {total_files} files")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 2:
|
|
sys.exit(__doc__)
|
|
main(sys.argv[1:])
|