Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions scripts/localize_changes.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
r'(?P<key>"(?:\\.|[^"\\])*")'

)
SWIFT_DEFAULT = re.compile(r'defaultValue\s*:\s*(?P<value>"(?:\\.|[^"\\])*")')
SWIFT_DEFAULT = re.compile(r'defaultValue\s*:\s*(?P<value>"""\n[\s\S]*?\n[ \t]*"""|"(?:\\.|[^"\\])*")')
SWIFT_COMMENT = re.compile(r'comment\s*:\s*(?P<comment>"(?:\\.|[^"\\])*")')
WEB_LOCALES = re.compile(r"export\s+const\s+locales\s*=\s*\[(?P<body>[\s\S]*?)\]\s*as\s+const")
QUOTED = re.compile(r'"((?:\\.|[^"\\])*)"|\'((?:\\.|[^\'\\])*)\'')
Expand Down Expand Up @@ -107,9 +107,12 @@ def base_text(root: Path, base: str, path: str) -> str:


def decode_swift_string(literal: str) -> str:
if not (literal.startswith('"') and literal.endswith('"')):
if literal.startswith('"""'):
value = multiline_swift_body(literal)
elif literal.startswith('"') and literal.endswith('"'):
value = literal[1:-1]
else:
raise ValueError("unsupported Swift string literal")
value = literal[1:-1]
if "\\(" in value:
raise ValueError("interpolated defaultValue requires manual catalog review")
result: list[str] = []
Expand All @@ -128,12 +131,38 @@ def decode_swift_string(literal: str) -> str:
return "".join(result)


def multiline_swift_body(literal: str) -> str:
"""Strip a multi-line literal's delimiters and the closing delimiter's indentation, as Swift does."""
lines = literal.split("\n")
indent = lines[-1][: -len('"""')]
if lines[0] != '"""' or indent.strip():
raise ValueError("unsupported Swift string literal")
body = []
for line in lines[1:-1]:
if line.strip() and not line.startswith(indent):
raise ValueError("unsupported Swift string literal")
if line.endswith("\\"):
raise ValueError("unsupported Swift escape in defaultValue")
body.append(line[len(indent):] if line.strip() else "")
return "\n".join(body)


def swift_call_suffix(text: str, start: int) -> str:
"""Read through the closing call parenthesis, respecting nested calls and strings."""
depth = 1
quoted = escaped = False
skip = 0
for index in range(start, len(text)):
char = text[index]
if skip:
skip -= 1
continue
if not quoted and text.startswith('"""', index):
end = text.find('"""', index + 3)
if end < 0:
return ""
skip = end + 2 - index
continue
if quoted:
if escaped:
escaped = False
Expand Down
14 changes: 14 additions & 0 deletions tests/test_localize_changes.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ def test_key_only_call_cannot_steal_next_default(self):
self.assertEqual({key: value.source for key, value in messages.items()}, {"other": "Other"})
self.assertTrue(any("cannot safely prepare" in item for item in attention))

def test_multiline_default_is_read_whole_and_deindented(self):
source = (
'let usage = String(localized: "cli.help", defaultValue: """\n'
' Usage: cmux thing\n'
'\n'
' --json Output JSON\n'
' """)\n'
'let next = String(localized: "cli.next", defaultValue: "Next")'
)
messages, attention = MODULE.parse_swift_messages("CLI/Thing.swift", source)
self.assertEqual(messages["cli.help"].source, "Usage: cmux thing\n\n --json Output JSON")
self.assertEqual(messages["cli.next"].source, "Next")
self.assertEqual(attention, [])

def test_import_validates_all_catalog_locale_groups_before_writing(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
Expand Down
Loading