-
Notifications
You must be signed in to change notification settings - Fork 25
/
Copy pathupdate_vehicle_components_translation.py
executable file
·208 lines (153 loc) · 7.03 KB
/
update_vehicle_components_translation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#!/usr/bin/env python3
"""
Extract translatable strings from all vehicle_components.json files to pygettext compatible format.
It also extracts all description strings from the vehicle_components_schema.json file.
It creates a vehicle_components.py python file that pygettext can process.
This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
SPDX-FileCopyrightText: 2024-2025 Amilcar Lucas
SPDX-License-Identifier: GPL-3.0-or-later
"""
import json
import logging
import os
from datetime import datetime
from typing import Optional, Union
# Set up a proper logger
logger = logging.getLogger(__name__)
def extract_keys_recursively(data: Union[dict, list], keys: Optional[set] = None) -> set[str]:
"""Recursively extract all keys from a nested JSON object."""
if keys is None:
keys = set()
if isinstance(data, dict):
for key, value in data.items():
# Only add string keys (skip numeric indices, version numbers, etc.)
if isinstance(key, str) and not key.isdigit():
keys.add(key)
# Recursively process nested structures
extract_keys_recursively(value, keys)
elif isinstance(data, list):
for item in data:
extract_keys_recursively(item, keys)
return keys
def gather_translatable_strings(templates_dir: str) -> list[str]:
"""
Scan all vehicle_components.json files and extract unique keys.
Args:
templates_dir: Directory containing vehicle templates
Returns:
Sorted list of translatable strings
"""
# Find all vehicle_components.json files recursively
json_files: list[str] = []
for root, _dirs, files in os.walk(templates_dir):
json_files.extend(os.path.join(root, file) for file in files if file == "vehicle_components.json")
# Extract unique keys from all JSON files
all_keys: set[str] = set()
for json_file in json_files:
try:
with open(json_file, encoding="utf-8") as f:
data = json.load(f)
keys = extract_keys_recursively(data)
all_keys.update(keys)
except json.JSONDecodeError as e: # noqa: PERF203
logger.error("Error parsing %s: %s", json_file, e)
except Exception as e: # pylint: disable=broad-exception-caught
logger.error("Error processing %s: %s", json_file, e)
return sorted(all_keys)
def extract_descriptions_from_schema(schema_file: str) -> list[str]:
"""
Extract all description strings from the schema file.
Args:
schema_file: Path to the schema file
Returns:
Sorted list of description strings
"""
descriptions: set[str] = set()
try:
with open(schema_file, encoding="utf-8") as f:
schema = json.load(f)
# Recursive function to extract all descriptions
def extract_descriptions_recursively(obj: Union[dict, list]) -> None:
if isinstance(obj, dict):
# Extract description if it exists
if "description" in obj and isinstance(obj["description"], str):
descriptions.add(obj["description"])
# Process all values recursively
for value in obj.values():
extract_descriptions_recursively(value)
elif isinstance(obj, list):
# Process list items recursively
for item in obj:
extract_descriptions_recursively(item)
# Start the extraction process
extract_descriptions_recursively(schema)
except json.JSONDecodeError as e:
logger.error("Error parsing schema file %s: %s", schema_file, e)
except Exception as e: # pylint: disable=broad-exception-caught
logger.error("Error processing schema file %s: %s", schema_file, e)
return sorted(descriptions)
def generate_vehicle_components_output(sorted_keys: list[str], sorted_descriptions: list[str], output_file: str) -> None:
"""
Generate the vehicle_components.py file with translatable strings.
Args:
sorted_keys: List of translatable strings to include
sorted_descriptions: List of description strings to include
output_file: Path to the output file
"""
# Generate the Python file content
current_year = datetime.now().year
file_content = f'''"""
Auto-generated by the update_vehicle_components_translation.py. Do not edit, ALL CHANGES WILL BE LOST.
This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator
SPDX-FileCopyrightText: 2024-{current_year} Amilcar Lucas
SPDX-License-Identifier: GPL-3.0-or-later
"""
from ardupilot_methodic_configurator import _
def translatable_strings() -> None:
"""
Translatable strings extracted from the all the vehicle_components.json files.
For pygettext to extract them, they have no other function
"""
'''
# Add each key as a translatable string
for key in sorted_keys:
file_content += f' _vehicle_components_strings = _("{key}")\n'
# Add the translatable descriptions function
file_content += '''
def translatable_descriptions() -> None: # noqa: PLR0915 # pylint: disable=too-many-statements
"""
Translatable strings extracted from the vehicle_components_schema.json file.
For pygettext to extract them, they have no other function
"""
'''
# Add each description as a translatable string
for description in sorted_descriptions:
# Escape any double quotes in the description
escaped_description = description.replace('"', '\\"')
file_content += f' _vehicle_components_descriptions = _("{escaped_description}")\n'
# Write to the output file
with open(output_file, "w", encoding="utf-8", newline="\n") as f:
f.write(file_content)
logger.info(
"Generated %s with %d translatable strings and %d description strings",
output_file,
len(sorted_keys),
len(sorted_descriptions),
)
def main() -> None:
"""Main function to coordinate the generation of translatable strings."""
# Configure logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
# Base directory for scanning
base_dir = os.path.dirname(os.path.abspath(__file__))
templates_dir = os.path.join(base_dir, "ardupilot_methodic_configurator", "vehicle_templates")
schema_file = os.path.join(base_dir, "ardupilot_methodic_configurator", "vehicle_components_schema.json")
output_file = os.path.join(base_dir, "ardupilot_methodic_configurator", "vehicle_components.py")
# Gather all translatable strings from component files
translatable_strings = gather_translatable_strings(templates_dir)
# Extract descriptions from schema file
translatable_descriptions = extract_descriptions_from_schema(schema_file)
# Generate the output file with both keys and descriptions
generate_vehicle_components_output(translatable_strings, translatable_descriptions, output_file)
if __name__ == "__main__":
main()