inav-toolkit 2.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- inav_toolkit/__init__.py +3 -0
- inav_toolkit/autotune.py +504 -0
- inav_toolkit/blackbox_analyzer.py +11070 -0
- inav_toolkit/flight_db.py +674 -0
- inav_toolkit/i18n.py +192 -0
- inav_toolkit/locales/en.json +187 -0
- inav_toolkit/locales/es.json +187 -0
- inav_toolkit/locales/pt_BR.json +187 -0
- inav_toolkit/msp.py +1293 -0
- inav_toolkit/param_analyzer.py +2832 -0
- inav_toolkit/vtol_configurator.py +856 -0
- inav_toolkit/wizard.py +1095 -0
- inav_toolkit-2.3.0.dist-info/METADATA +296 -0
- inav_toolkit-2.3.0.dist-info/RECORD +18 -0
- inav_toolkit-2.3.0.dist-info/WHEEL +5 -0
- inav_toolkit-2.3.0.dist-info/entry_points.txt +5 -0
- inav_toolkit-2.3.0.dist-info/licenses/LICENSE +674 -0
- inav_toolkit-2.3.0.dist-info/top_level.txt +1 -0
inav_toolkit/i18n.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Internationalization (i18n) for INAV Toolkit.
|
|
2
|
+
|
|
3
|
+
Lightweight JSON-based translation system. No external dependencies.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
from inav_toolkit.i18n import t, set_locale
|
|
7
|
+
|
|
8
|
+
set_locale("pt_BR") # or "es", "en", etc.
|
|
9
|
+
print(t("verdict.dialed_in"))
|
|
10
|
+
print(t("quality.too_short", duration="1.2"))
|
|
11
|
+
|
|
12
|
+
Translation keys use dotted notation (e.g., "verdict.dialed_in").
|
|
13
|
+
Missing keys fall back to English. Missing English keys return the key itself.
|
|
14
|
+
Technical terms (PID, Hz, Roll/Pitch/Yaw, CLI commands) stay untranslated.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import locale as _locale_mod
|
|
20
|
+
|
|
21
|
+
_catalogs = {} # lang -> {key: translated_string}
|
|
22
|
+
_active_locale = "en"
|
|
23
|
+
_locales_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "locales")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _read_json_resource(filename):
|
|
27
|
+
"""Read a JSON file from the locales directory.
|
|
28
|
+
|
|
29
|
+
Tries importlib.resources first (works in all install modes),
|
|
30
|
+
then falls back to direct filesystem access.
|
|
31
|
+
Returns parsed dict or None on failure.
|
|
32
|
+
"""
|
|
33
|
+
# Strategy 1: importlib.resources (Python 3.9+, works for wheels/eggs/editable)
|
|
34
|
+
try:
|
|
35
|
+
from importlib.resources import files
|
|
36
|
+
ref = files("inav_toolkit").joinpath("locales", filename)
|
|
37
|
+
text = ref.read_text(encoding="utf-8")
|
|
38
|
+
return json.loads(text)
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
# Strategy 2: direct filesystem path relative to this file
|
|
43
|
+
path = os.path.join(_locales_dir, filename)
|
|
44
|
+
if os.path.isfile(path):
|
|
45
|
+
try:
|
|
46
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
47
|
+
return json.load(f)
|
|
48
|
+
except (json.JSONDecodeError, OSError):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _load_catalog(lang):
|
|
55
|
+
"""Load a locale JSON catalog. Returns dict or empty dict on failure."""
|
|
56
|
+
if lang in _catalogs:
|
|
57
|
+
return _catalogs[lang]
|
|
58
|
+
|
|
59
|
+
data = _read_json_resource(f"{lang}.json")
|
|
60
|
+
_catalogs[lang] = data if data is not None else {}
|
|
61
|
+
return _catalogs[lang]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def set_locale(lang):
|
|
65
|
+
"""Set the active locale. Loads the catalog if not already loaded.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
lang: Language code, e.g., "en", "pt_BR", "es".
|
|
69
|
+
Also accepts formats like "pt-BR", "pt_BR.UTF-8".
|
|
70
|
+
"""
|
|
71
|
+
global _active_locale
|
|
72
|
+
|
|
73
|
+
# Normalize: "pt-BR" -> "pt_BR", "pt_BR.UTF-8" -> "pt_BR"
|
|
74
|
+
lang = lang.replace("-", "_").split(".")[0]
|
|
75
|
+
|
|
76
|
+
_load_catalog(lang)
|
|
77
|
+
|
|
78
|
+
# Also preload base language (e.g., "pt" from "pt_BR") for fallback
|
|
79
|
+
base = lang.split("_")[0]
|
|
80
|
+
if base != lang:
|
|
81
|
+
_load_catalog(base)
|
|
82
|
+
|
|
83
|
+
# Always ensure English is loaded as final fallback
|
|
84
|
+
_load_catalog("en")
|
|
85
|
+
|
|
86
|
+
_active_locale = lang
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_locale():
|
|
90
|
+
"""Return the current active locale code."""
|
|
91
|
+
return _active_locale
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def detect_locale():
|
|
95
|
+
"""Auto-detect locale from environment.
|
|
96
|
+
|
|
97
|
+
Priority: INAV_LANG env var -> LANG/LC_ALL -> "en"
|
|
98
|
+
"""
|
|
99
|
+
# Check INAV-specific env var first
|
|
100
|
+
env_lang = os.environ.get("INAV_LANG", "")
|
|
101
|
+
if env_lang:
|
|
102
|
+
return env_lang.replace("-", "_").split(".")[0]
|
|
103
|
+
|
|
104
|
+
# System locale
|
|
105
|
+
try:
|
|
106
|
+
sys_locale = _locale_mod.getdefaultlocale()[0] or ""
|
|
107
|
+
if sys_locale:
|
|
108
|
+
return sys_locale.replace("-", "_").split(".")[0]
|
|
109
|
+
except (ValueError, AttributeError):
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
# Fallback to LANG env var directly
|
|
113
|
+
lang_env = os.environ.get("LANG", os.environ.get("LC_ALL", ""))
|
|
114
|
+
if lang_env:
|
|
115
|
+
return lang_env.replace("-", "_").split(".")[0]
|
|
116
|
+
|
|
117
|
+
return "en"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def available_locales():
|
|
121
|
+
"""List available locale codes (based on JSON files in locales/)."""
|
|
122
|
+
locales = []
|
|
123
|
+
|
|
124
|
+
# Strategy 1: importlib.resources
|
|
125
|
+
try:
|
|
126
|
+
from importlib.resources import files
|
|
127
|
+
locales_ref = files("inav_toolkit").joinpath("locales")
|
|
128
|
+
for item in locales_ref.iterdir():
|
|
129
|
+
name = item.name if hasattr(item, 'name') else str(item).rsplit("/", 1)[-1]
|
|
130
|
+
if name.endswith(".json"):
|
|
131
|
+
locales.append(name[:-5])
|
|
132
|
+
if locales:
|
|
133
|
+
return sorted(locales)
|
|
134
|
+
except Exception:
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
# Strategy 2: filesystem
|
|
138
|
+
if os.path.isdir(_locales_dir):
|
|
139
|
+
for f in sorted(os.listdir(_locales_dir)):
|
|
140
|
+
if f.endswith(".json"):
|
|
141
|
+
locales.append(f[:-5])
|
|
142
|
+
return locales
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def t(key, **kwargs):
|
|
146
|
+
"""Translate a key, with optional format substitution.
|
|
147
|
+
|
|
148
|
+
Fallback chain: active locale -> base language -> English -> key itself.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
key: Dotted translation key, e.g., "verdict.dialed_in"
|
|
152
|
+
**kwargs: Format substitution values, e.g., duration="1.2"
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Translated string with substitutions applied.
|
|
156
|
+
|
|
157
|
+
Examples:
|
|
158
|
+
t("verdict.dialed_in")
|
|
159
|
+
t("quality.too_short", duration="1.2")
|
|
160
|
+
"""
|
|
161
|
+
# Try active locale
|
|
162
|
+
catalog = _catalogs.get(_active_locale, {})
|
|
163
|
+
text = catalog.get(key)
|
|
164
|
+
|
|
165
|
+
# Fallback to base language (e.g., "pt" from "pt_BR")
|
|
166
|
+
if text is None:
|
|
167
|
+
base = _active_locale.split("_")[0]
|
|
168
|
+
if base != _active_locale:
|
|
169
|
+
catalog = _catalogs.get(base, {})
|
|
170
|
+
text = catalog.get(key)
|
|
171
|
+
|
|
172
|
+
# Fallback to English
|
|
173
|
+
if text is None:
|
|
174
|
+
catalog = _catalogs.get("en", {})
|
|
175
|
+
text = catalog.get(key)
|
|
176
|
+
|
|
177
|
+
# Final fallback: return key itself
|
|
178
|
+
if text is None:
|
|
179
|
+
text = key
|
|
180
|
+
|
|
181
|
+
# Apply format substitution
|
|
182
|
+
if kwargs:
|
|
183
|
+
try:
|
|
184
|
+
text = text.format(**kwargs)
|
|
185
|
+
except (KeyError, IndexError, ValueError):
|
|
186
|
+
pass # Return template as-is if substitution fails
|
|
187
|
+
|
|
188
|
+
return text
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# Auto-initialize English catalog on import
|
|
192
|
+
_load_catalog("en")
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"locale": "en",
|
|
4
|
+
"name": "English",
|
|
5
|
+
"authors": ["INAV Toolkit contributors"],
|
|
6
|
+
"note": "Reference catalog. Keys use dotted notation. {placeholders} are substituted at runtime. Technical terms (PID, Hz, Roll/Pitch/Yaw, LPF, CLI commands) stay untranslated in all locales."
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
"banner.analyzer": "INAV Blackbox Analyzer",
|
|
10
|
+
"banner.nav_analyzer": "INAV Nav Analyzer",
|
|
11
|
+
"banner.replay": "INAV Flight Replay",
|
|
12
|
+
"banner.compare": "INAV Flight Comparison",
|
|
13
|
+
"banner.quality_check": "Log Quality Check",
|
|
14
|
+
"banner.loading": "Loading: {file}",
|
|
15
|
+
"banner.craft": "Aircraft",
|
|
16
|
+
"banner.firmware": "Firmware",
|
|
17
|
+
"banner.platform": "Platform",
|
|
18
|
+
"banner.frame": "Frame",
|
|
19
|
+
"banner.props": "Props",
|
|
20
|
+
"banner.battery": "Battery",
|
|
21
|
+
"banner.motors": "Motors",
|
|
22
|
+
"banner.profile": "Profile",
|
|
23
|
+
"banner.filters": "Filters",
|
|
24
|
+
|
|
25
|
+
"quality.title": "Log Quality",
|
|
26
|
+
"quality.good": "GOOD",
|
|
27
|
+
"quality.marginal": "MARGINAL",
|
|
28
|
+
"quality.unusable": "UNUSABLE",
|
|
29
|
+
"quality.all_passed": "All quality checks passed",
|
|
30
|
+
"quality.not_usable": "Log is not usable for analysis. Run with --check-log for details.",
|
|
31
|
+
"quality.fields_label": "Fields",
|
|
32
|
+
"quality.active": "active",
|
|
33
|
+
"quality.idle": "idle",
|
|
34
|
+
"quality.too_short": "Log is only {duration}s — need at least 5s for analysis",
|
|
35
|
+
"quality.short": "Log is short ({duration}s) — results may be unreliable, 10s+ recommended",
|
|
36
|
+
"quality.low_sr_fail": "Sample rate {sr}Hz is too low for spectral analysis (need 100Hz+)",
|
|
37
|
+
"quality.low_sr_warn": "Sample rate {sr}Hz limits noise analysis to {nyquist}Hz (500Hz+ preferred)",
|
|
38
|
+
"quality.few_samples": "Only {n} samples — need at least {min} for FFT",
|
|
39
|
+
"quality.no_gyro": "No gyro data found — cannot perform noise or PID analysis",
|
|
40
|
+
"quality.no_motors": "No motor data — motor balance analysis unavailable",
|
|
41
|
+
"quality.no_setpoint": "No setpoint/rcCommand data — PID response analysis unavailable",
|
|
42
|
+
"quality.no_stick": "No stick movement detected — log may be ground-only (armed but not flying)",
|
|
43
|
+
"quality.zeros_gyro": "Gyro {axis} is all zeros — sensor may be dead or not logging",
|
|
44
|
+
"quality.zeros_motor": "Motor {n} output is all zeros — motor disabled or not armed?",
|
|
45
|
+
"quality.corrupt_fail": "{pct}% of frames corrupt — log may be damaged",
|
|
46
|
+
"quality.corrupt_warn": "{pct}% of frames had decode errors",
|
|
47
|
+
"quality.nan_fail": "Gyro {axis} has {pct}% NaN values — data largely missing",
|
|
48
|
+
"quality.nan_warn": "Gyro {axis} has {pct}% NaN values — some data gaps",
|
|
49
|
+
|
|
50
|
+
"verdict.dialed_in": "This tune is dialed in. You're done — go fly!",
|
|
51
|
+
"verdict.nearly_there": "Almost there — just minor tweaks left.",
|
|
52
|
+
"verdict.getting_better": "Making progress. Apply the changes below and fly again.",
|
|
53
|
+
"verdict.needs_work": "Significant tuning needed. Start with the top priorities.",
|
|
54
|
+
"verdict.rough": "Needs attention. Focus on filters first, then PIDs.",
|
|
55
|
+
"verdict.need_data_good": "Noise and motors look good, but PID performance could not be measured. Fly with stick inputs (rolls, pitches, yaw sweeps) for tuning data.",
|
|
56
|
+
"verdict.need_data_bad": "PID performance could not be measured. Fly with deliberate stick inputs for tuning data.",
|
|
57
|
+
"verdict.idle": "Motors were at idle throughout this log — the quad was armed but not flying. Hover for 15+ seconds with gentle stick inputs for meaningful analysis.",
|
|
58
|
+
|
|
59
|
+
"section.scores": "Scores",
|
|
60
|
+
"section.overall": "Overall",
|
|
61
|
+
"section.noise": "Noise",
|
|
62
|
+
"section.pid": "PID Response",
|
|
63
|
+
"section.motor": "Motor Balance",
|
|
64
|
+
"section.oscillation": "Oscillation",
|
|
65
|
+
"section.findings": "Findings",
|
|
66
|
+
"section.noise_sources": "Noise Sources",
|
|
67
|
+
"section.recommended": "Recommended Changes",
|
|
68
|
+
"section.deferred": "Deferred (apply after test flight)",
|
|
69
|
+
"section.config_changes": "Config Changes",
|
|
70
|
+
"section.motor_balance": "Motor Balance",
|
|
71
|
+
"section.comparison": "Score Comparison",
|
|
72
|
+
"section.verdict": "Verdict",
|
|
73
|
+
"section.duration": "Duration",
|
|
74
|
+
"section.actions_remaining": "Actions Remaining",
|
|
75
|
+
"section.noise_overlay": "Noise Spectrum Overlay",
|
|
76
|
+
"section.pid_compare": "PID Response Comparison",
|
|
77
|
+
"section.flight_modes": "Flight Modes",
|
|
78
|
+
"section.spectrogram": "Noise Spectrogram (Waterfall)",
|
|
79
|
+
|
|
80
|
+
"compare.improved": "Tune improved",
|
|
81
|
+
"compare.degraded": "Tune degraded",
|
|
82
|
+
"compare.same": "Roughly the same",
|
|
83
|
+
"compare.improved_by": "Overall improved by {delta} points",
|
|
84
|
+
"compare.degraded_by": "Overall degraded by {delta} points",
|
|
85
|
+
"compare.score_same": "Overall score roughly the same",
|
|
86
|
+
"compare.diff_craft": "Different craft names: \"{a}\" vs \"{b}\"",
|
|
87
|
+
"compare.diff_craft_note": "Comparison still works but thresholds may differ.",
|
|
88
|
+
"compare.diff_profile": "Different frame profiles: {a} vs {b}",
|
|
89
|
+
"compare.diff_profile_note": "Score thresholds differ — deltas may reflect profile, not tuning.",
|
|
90
|
+
"compare.analyzing_a": "Analyzing flight A...",
|
|
91
|
+
"compare.analyzing_b": "Analyzing flight B...",
|
|
92
|
+
"compare.generating": "Generating comparison report...",
|
|
93
|
+
"compare.change": "Change",
|
|
94
|
+
"compare.metric": "Metric",
|
|
95
|
+
"compare.spread": "Spread",
|
|
96
|
+
"compare.peak_sat": "Peak Sat",
|
|
97
|
+
|
|
98
|
+
"replay.synced_zoom": "synced zoom/pan",
|
|
99
|
+
"replay.flight_mode_bar": "flight mode overlay",
|
|
100
|
+
"replay.webgl": "WebGL rendering",
|
|
101
|
+
"replay.panels": "Panels: gyro×3, motors, spectrogram waterfall, throttle",
|
|
102
|
+
"replay.features": "Features: {features}",
|
|
103
|
+
"replay.no_mode_data": "No flight mode data (S-frames)",
|
|
104
|
+
"replay.computing_spectrogram": "Computing spectrogram...",
|
|
105
|
+
"replay.generating": "Generating Plotly.js replay (WebGL)...",
|
|
106
|
+
|
|
107
|
+
"report.generated_by": "Generated by",
|
|
108
|
+
"report.state_saved": "State: {path} (use --previous on next run)",
|
|
109
|
+
"report.html_saved": "Report: {path}",
|
|
110
|
+
"report.markdown_saved": "Markdown: {path} (paste into forum/Discord)",
|
|
111
|
+
"report.test_with_props_off": "Test changes with props off first",
|
|
112
|
+
|
|
113
|
+
"label.setting": "Setting",
|
|
114
|
+
"label.current": "Current",
|
|
115
|
+
"label.recommended": "Recommended",
|
|
116
|
+
"label.reason": "Reason",
|
|
117
|
+
"label.axis": "Axis",
|
|
118
|
+
"label.delay": "Delay",
|
|
119
|
+
"label.overshoot": "Overshoot",
|
|
120
|
+
"label.avg": "Avg",
|
|
121
|
+
"label.stddev": "StdDev",
|
|
122
|
+
"label.saturation": "Saturation",
|
|
123
|
+
"label.motor": "Motor",
|
|
124
|
+
"label.frequency": "Frequency",
|
|
125
|
+
"label.power": "Power",
|
|
126
|
+
"label.confidence": "confidence",
|
|
127
|
+
"label.score": "Score",
|
|
128
|
+
"label.date": "Date",
|
|
129
|
+
|
|
130
|
+
"terminal.no_changes": "No changes needed — go fly!",
|
|
131
|
+
"terminal.need_stick_data": "Need flight data with stick inputs to measure PID response.",
|
|
132
|
+
"terminal.progression": "Progression",
|
|
133
|
+
"terminal.improving": "Improving",
|
|
134
|
+
"terminal.degrading": "Degrading",
|
|
135
|
+
"terminal.stable": "Stable",
|
|
136
|
+
"terminal.done": "done",
|
|
137
|
+
"terminal.analyzing": "Analyzing...",
|
|
138
|
+
"terminal.parsing_headers": "Parsing {n} header parameters...",
|
|
139
|
+
"terminal.decoding": "Decoding binary log (native)...",
|
|
140
|
+
"terminal.parsing_csv": "Parsing CSV...",
|
|
141
|
+
"terminal.nav_data": "Nav data",
|
|
142
|
+
"terminal.no_nav_fields": "Warning: No navigation fields found in this log",
|
|
143
|
+
"terminal.no_pid_values": "Warning: No PID values in headers — use .bbl for exact recommendations",
|
|
144
|
+
|
|
145
|
+
"severity.critical": "CRITICAL",
|
|
146
|
+
"severity.important": "IMPORTANT",
|
|
147
|
+
"severity.moderate": "MODERATE",
|
|
148
|
+
"severity.minor": "MINOR",
|
|
149
|
+
"severity.info": "INFO",
|
|
150
|
+
|
|
151
|
+
"finding.osc_p_too_high": "Hover oscillation — reduce P from {current} to {new}",
|
|
152
|
+
"finding.osc_pd_interaction": "Oscillation: P/D interaction — reduce both P and D",
|
|
153
|
+
"finding.osc_d_noise": "D-term noise amplification causing oscillation",
|
|
154
|
+
"finding.osc_filter_gap": "High-frequency noise leaking through filters",
|
|
155
|
+
"finding.osc_unknown": "Hover oscillation detected — reduce P by 25%",
|
|
156
|
+
"finding.phase_lag_high": "Total filter phase lag: {degrees}° ({ms}ms) at {freq}Hz",
|
|
157
|
+
"finding.enable_dyn_notch": "Noise peaks detected — dynamic notch will track and attenuate these",
|
|
158
|
+
"finding.enable_rpm": "RPM filter tracks motor noise precisely — requires ESC telemetry or bidirectional DSHOT",
|
|
159
|
+
"finding.gyro_lpf_adjust": "Gyro lowpass filter: {direction} from {current}Hz to {new}Hz",
|
|
160
|
+
"finding.dterm_lpf_adjust": "D-term lowpass filter: Reduce from {current}Hz to {new}Hz",
|
|
161
|
+
|
|
162
|
+
"sanity.title": "INAV Pre-Flight Sanity Check",
|
|
163
|
+
"sanity.go": "GO FOR FLIGHT",
|
|
164
|
+
"sanity.no_go": "NO-GO — Fix issues before flying",
|
|
165
|
+
"sanity.caution": "CAUTION — Review warnings before flying",
|
|
166
|
+
"sanity.category.mixing": "Motor/Servo Mixing",
|
|
167
|
+
"sanity.category.modes": "Flight Modes",
|
|
168
|
+
"sanity.category.sensors": "Sensor Configuration",
|
|
169
|
+
"sanity.category.failsafe": "Failsafe",
|
|
170
|
+
"sanity.category.battery": "Battery Monitoring",
|
|
171
|
+
"sanity.category.pid_range": "PID Range Check",
|
|
172
|
+
"sanity.category.filters": "Filter Configuration",
|
|
173
|
+
"sanity.category.rates": "Rates & Limits",
|
|
174
|
+
"sanity.category.gps_nav": "GPS & Navigation",
|
|
175
|
+
"sanity.category.motor_protocol": "Motor Protocol",
|
|
176
|
+
"sanity.category.osd": "OSD Configuration",
|
|
177
|
+
"sanity.category.arming": "Arming Safety",
|
|
178
|
+
"sanity.category.servo": "Servo Configuration",
|
|
179
|
+
"sanity.category.vtol": "VTOL Configuration",
|
|
180
|
+
"sanity.category.board": "Board & Port Setup",
|
|
181
|
+
"sanity.category.misc": "Miscellaneous",
|
|
182
|
+
|
|
183
|
+
"error.file_not_found": "File not found: {path}",
|
|
184
|
+
"error.not_inav": "Not an INAV FC (got: {variant})",
|
|
185
|
+
"error.no_logfile": "No logfile specified. Use: inav-analyze flight.bbl",
|
|
186
|
+
"error.compare_not_found": "Comparison file not found: {path}"
|
|
187
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"locale": "es",
|
|
4
|
+
"name": "Español",
|
|
5
|
+
"authors": ["INAV Toolkit contributors"],
|
|
6
|
+
"note": "Términos técnicos (PID, Hz, Roll/Pitch/Yaw, LPF, comandos CLI) permanecen en inglés."
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
"banner.analyzer": "INAV Analizador de Blackbox",
|
|
10
|
+
"banner.nav_analyzer": "INAV Analizador de Navegación",
|
|
11
|
+
"banner.replay": "INAV Reproducción de Vuelo",
|
|
12
|
+
"banner.compare": "INAV Comparación de Vuelos",
|
|
13
|
+
"banner.quality_check": "Verificación de Calidad del Log",
|
|
14
|
+
"banner.loading": "Cargando: {file}",
|
|
15
|
+
"banner.craft": "Aeronave",
|
|
16
|
+
"banner.firmware": "Firmware",
|
|
17
|
+
"banner.platform": "Plataforma",
|
|
18
|
+
"banner.frame": "Frame",
|
|
19
|
+
"banner.props": "Hélices",
|
|
20
|
+
"banner.battery": "Batería",
|
|
21
|
+
"banner.motors": "Motores",
|
|
22
|
+
"banner.profile": "Perfil",
|
|
23
|
+
"banner.filters": "Filtros",
|
|
24
|
+
|
|
25
|
+
"quality.title": "Calidad del Log",
|
|
26
|
+
"quality.good": "BUENO",
|
|
27
|
+
"quality.marginal": "MARGINAL",
|
|
28
|
+
"quality.unusable": "INUTILIZABLE",
|
|
29
|
+
"quality.all_passed": "Todas las verificaciones de calidad aprobadas",
|
|
30
|
+
"quality.not_usable": "El log no es utilizable para análisis. Ejecute con --check-log para detalles.",
|
|
31
|
+
"quality.fields_label": "Campos",
|
|
32
|
+
"quality.active": "activo",
|
|
33
|
+
"quality.idle": "inactivo",
|
|
34
|
+
"quality.too_short": "El log tiene solo {duration}s — se necesitan al menos 5s para análisis",
|
|
35
|
+
"quality.short": "El log es corto ({duration}s) — los resultados pueden ser imprecisos, se recomiendan 10s+",
|
|
36
|
+
"quality.low_sr_fail": "Tasa de muestreo {sr}Hz es demasiado baja para análisis espectral (se necesitan 100Hz+)",
|
|
37
|
+
"quality.low_sr_warn": "Tasa de muestreo {sr}Hz limita el análisis de ruido a {nyquist}Hz (preferible 500Hz+)",
|
|
38
|
+
"quality.few_samples": "Solo {n} muestras — se necesitan al menos {min} para FFT",
|
|
39
|
+
"quality.no_gyro": "No se encontraron datos del giroscopio — no se puede analizar ruido ni PID",
|
|
40
|
+
"quality.no_motors": "Sin datos de motor — análisis de balance no disponible",
|
|
41
|
+
"quality.no_setpoint": "Sin datos de setpoint/rcCommand — análisis de respuesta PID no disponible",
|
|
42
|
+
"quality.no_stick": "No se detectó movimiento de stick — el log puede ser solo de tierra (armado pero sin volar)",
|
|
43
|
+
"quality.zeros_gyro": "Gyro {axis} está todo en ceros — el sensor puede estar muerto o sin registro",
|
|
44
|
+
"quality.zeros_motor": "Motor {n} está todo en ceros — ¿motor desactivado o no armado?",
|
|
45
|
+
"quality.corrupt_fail": "{pct}% de los frames corruptos — el log puede estar dañado",
|
|
46
|
+
"quality.corrupt_warn": "{pct}% de los frames tuvieron errores de decodificación",
|
|
47
|
+
"quality.nan_fail": "Gyro {axis} tiene {pct}% valores NaN — datos en gran parte ausentes",
|
|
48
|
+
"quality.nan_warn": "Gyro {axis} tiene {pct}% valores NaN — algunas lagunas en los datos",
|
|
49
|
+
|
|
50
|
+
"verdict.dialed_in": "El tune está perfecto. ¡Listo para volar!",
|
|
51
|
+
"verdict.nearly_there": "Casi listo — solo quedan ajustes menores.",
|
|
52
|
+
"verdict.getting_better": "Progresando. Aplique los cambios a continuación y vuele de nuevo.",
|
|
53
|
+
"verdict.needs_work": "Se necesita ajuste significativo. Comience con las prioridades.",
|
|
54
|
+
"verdict.rough": "Necesita atención. Enfóquese en los filtros primero, luego en los PIDs.",
|
|
55
|
+
"verdict.need_data_good": "Ruido y motores se ven bien, pero no se pudo medir el rendimiento PID. Vuele con movimientos de stick (rolls, pitches, barridos de yaw) para datos de tune.",
|
|
56
|
+
"verdict.need_data_bad": "No se pudo medir el rendimiento PID. Vuele con movimientos deliberados de stick para datos de tune.",
|
|
57
|
+
"verdict.idle": "Los motores estuvieron en idle durante todo el log — el quad estaba armado pero no voló. Haga hover por 15+ segundos con movimientos suaves de stick para análisis significativo.",
|
|
58
|
+
|
|
59
|
+
"section.scores": "Puntuaciones",
|
|
60
|
+
"section.overall": "General",
|
|
61
|
+
"section.noise": "Ruido",
|
|
62
|
+
"section.pid": "Respuesta PID",
|
|
63
|
+
"section.motor": "Balance de Motores",
|
|
64
|
+
"section.oscillation": "Oscilación",
|
|
65
|
+
"section.findings": "Diagnóstico",
|
|
66
|
+
"section.noise_sources": "Fuentes de Ruido",
|
|
67
|
+
"section.recommended": "Cambios Recomendados",
|
|
68
|
+
"section.deferred": "Diferido (aplicar después del vuelo de prueba)",
|
|
69
|
+
"section.config_changes": "Cambios de Configuración",
|
|
70
|
+
"section.motor_balance": "Balance de Motores",
|
|
71
|
+
"section.comparison": "Comparación de Puntuaciones",
|
|
72
|
+
"section.verdict": "Veredicto",
|
|
73
|
+
"section.duration": "Duración",
|
|
74
|
+
"section.actions_remaining": "Acciones Restantes",
|
|
75
|
+
"section.noise_overlay": "Superposición de Espectro de Ruido",
|
|
76
|
+
"section.pid_compare": "Comparación de Respuesta PID",
|
|
77
|
+
"section.flight_modes": "Modos de Vuelo",
|
|
78
|
+
"section.spectrogram": "Espectrograma de Ruido (Cascada)",
|
|
79
|
+
|
|
80
|
+
"compare.improved": "El tune mejoró",
|
|
81
|
+
"compare.degraded": "El tune empeoró",
|
|
82
|
+
"compare.same": "Prácticamente igual",
|
|
83
|
+
"compare.improved_by": "General mejoró {delta} puntos",
|
|
84
|
+
"compare.degraded_by": "General empeoró {delta} puntos",
|
|
85
|
+
"compare.score_same": "Puntuación general prácticamente igual",
|
|
86
|
+
"compare.diff_craft": "Nombres de aeronaves diferentes: \"{a}\" vs \"{b}\"",
|
|
87
|
+
"compare.diff_craft_note": "La comparación funciona pero los umbrales pueden diferir.",
|
|
88
|
+
"compare.diff_profile": "Perfiles de frame diferentes: {a} vs {b}",
|
|
89
|
+
"compare.diff_profile_note": "Los umbrales de puntuación difieren — los deltas pueden reflejar el perfil, no el tune.",
|
|
90
|
+
"compare.analyzing_a": "Analizando vuelo A...",
|
|
91
|
+
"compare.analyzing_b": "Analizando vuelo B...",
|
|
92
|
+
"compare.generating": "Generando informe de comparación...",
|
|
93
|
+
"compare.change": "Cambio",
|
|
94
|
+
"compare.metric": "Métrica",
|
|
95
|
+
"compare.spread": "Dispersión",
|
|
96
|
+
"compare.peak_sat": "Saturación Pico",
|
|
97
|
+
|
|
98
|
+
"replay.synced_zoom": "zoom/pan sincronizado",
|
|
99
|
+
"replay.flight_mode_bar": "barra de modos de vuelo",
|
|
100
|
+
"replay.webgl": "renderizado WebGL",
|
|
101
|
+
"replay.panels": "Paneles: gyro×3, motores, espectrograma cascada, throttle",
|
|
102
|
+
"replay.features": "Características: {features}",
|
|
103
|
+
"replay.no_mode_data": "Sin datos de modo de vuelo (S-frames)",
|
|
104
|
+
"replay.computing_spectrogram": "Calculando espectrograma...",
|
|
105
|
+
"replay.generating": "Generando replay Plotly.js (WebGL)...",
|
|
106
|
+
|
|
107
|
+
"report.generated_by": "Generado por",
|
|
108
|
+
"report.state_saved": "Estado: {path} (use --previous en la próxima ejecución)",
|
|
109
|
+
"report.html_saved": "Informe: {path}",
|
|
110
|
+
"report.markdown_saved": "Markdown: {path} (pegue en foro/Discord)",
|
|
111
|
+
"report.test_with_props_off": "Pruebe los cambios con las hélices quitadas primero",
|
|
112
|
+
|
|
113
|
+
"label.setting": "Configuración",
|
|
114
|
+
"label.current": "Actual",
|
|
115
|
+
"label.recommended": "Recomendado",
|
|
116
|
+
"label.reason": "Razón",
|
|
117
|
+
"label.axis": "Eje",
|
|
118
|
+
"label.delay": "Retardo",
|
|
119
|
+
"label.overshoot": "Overshoot",
|
|
120
|
+
"label.avg": "Prom",
|
|
121
|
+
"label.stddev": "DesvEst",
|
|
122
|
+
"label.saturation": "Saturación",
|
|
123
|
+
"label.motor": "Motor",
|
|
124
|
+
"label.frequency": "Frecuencia",
|
|
125
|
+
"label.power": "Potencia",
|
|
126
|
+
"label.confidence": "confianza",
|
|
127
|
+
"label.score": "Puntuación",
|
|
128
|
+
"label.date": "Fecha",
|
|
129
|
+
|
|
130
|
+
"terminal.no_changes": "¡No se necesitan cambios — a volar!",
|
|
131
|
+
"terminal.need_stick_data": "Se necesitan datos de vuelo con movimientos de stick para medir respuesta PID.",
|
|
132
|
+
"terminal.progression": "Progresión",
|
|
133
|
+
"terminal.improving": "Mejorando",
|
|
134
|
+
"terminal.degrading": "Empeorando",
|
|
135
|
+
"terminal.stable": "Estable",
|
|
136
|
+
"terminal.done": "listo",
|
|
137
|
+
"terminal.analyzing": "Analizando...",
|
|
138
|
+
"terminal.parsing_headers": "Procesando {n} parámetros del encabezado...",
|
|
139
|
+
"terminal.decoding": "Decodificando log binario (nativo)...",
|
|
140
|
+
"terminal.parsing_csv": "Procesando CSV...",
|
|
141
|
+
"terminal.nav_data": "Datos de navegación",
|
|
142
|
+
"terminal.no_nav_fields": "Aviso: No se encontraron campos de navegación en este log",
|
|
143
|
+
"terminal.no_pid_values": "Aviso: Sin valores PID en los encabezados — use .bbl para recomendaciones exactas",
|
|
144
|
+
|
|
145
|
+
"severity.critical": "CRÍTICO",
|
|
146
|
+
"severity.important": "IMPORTANTE",
|
|
147
|
+
"severity.moderate": "MODERADO",
|
|
148
|
+
"severity.minor": "MENOR",
|
|
149
|
+
"severity.info": "INFO",
|
|
150
|
+
|
|
151
|
+
"finding.osc_p_too_high": "Oscilación en hover — reduzca P de {current} a {new}",
|
|
152
|
+
"finding.osc_pd_interaction": "Oscilación: interacción P/D — reduzca ambos P y D",
|
|
153
|
+
"finding.osc_d_noise": "Amplificación de ruido en el D-term causando oscilación",
|
|
154
|
+
"finding.osc_filter_gap": "Ruido de alta frecuencia filtrándose a través de los filtros",
|
|
155
|
+
"finding.osc_unknown": "Oscilación en hover detectada — reduzca P en 25%",
|
|
156
|
+
"finding.phase_lag_high": "Retardo total de fase de los filtros: {degrees}° ({ms}ms) a {freq}Hz",
|
|
157
|
+
"finding.enable_dyn_notch": "Picos de ruido detectados — el notch dinámico rastreará y atenuará estos",
|
|
158
|
+
"finding.enable_rpm": "El filtro RPM rastrea el ruido de los motores con precisión — requiere telemetría ESC o DSHOT bidireccional",
|
|
159
|
+
"finding.gyro_lpf_adjust": "Filtro lowpass del giroscopio: {direction} de {current}Hz a {new}Hz",
|
|
160
|
+
"finding.dterm_lpf_adjust": "Filtro lowpass del D-term: Reducir de {current}Hz a {new}Hz",
|
|
161
|
+
|
|
162
|
+
"sanity.title": "INAV Verificación Pre-Vuelo",
|
|
163
|
+
"sanity.go": "APROBADO PARA VUELO",
|
|
164
|
+
"sanity.no_go": "NO APROBADO — Corrija los problemas antes de volar",
|
|
165
|
+
"sanity.caution": "PRECAUCIÓN — Revise los avisos antes de volar",
|
|
166
|
+
"sanity.category.mixing": "Mezcla Motor/Servo",
|
|
167
|
+
"sanity.category.modes": "Modos de Vuelo",
|
|
168
|
+
"sanity.category.sensors": "Configuración de Sensores",
|
|
169
|
+
"sanity.category.failsafe": "Failsafe",
|
|
170
|
+
"sanity.category.battery": "Monitoreo de Batería",
|
|
171
|
+
"sanity.category.pid_range": "Verificación de Rango PID",
|
|
172
|
+
"sanity.category.filters": "Configuración de Filtros",
|
|
173
|
+
"sanity.category.rates": "Rates y Límites",
|
|
174
|
+
"sanity.category.gps_nav": "GPS y Navegación",
|
|
175
|
+
"sanity.category.motor_protocol": "Protocolo de Motor",
|
|
176
|
+
"sanity.category.osd": "Configuración de OSD",
|
|
177
|
+
"sanity.category.arming": "Seguridad de Armado",
|
|
178
|
+
"sanity.category.servo": "Configuración de Servo",
|
|
179
|
+
"sanity.category.vtol": "Configuración VTOL",
|
|
180
|
+
"sanity.category.board": "Configuración de Placa y Puertos",
|
|
181
|
+
"sanity.category.misc": "Varios",
|
|
182
|
+
|
|
183
|
+
"error.file_not_found": "Archivo no encontrado: {path}",
|
|
184
|
+
"error.not_inav": "No es un FC INAV (recibido: {variant})",
|
|
185
|
+
"error.no_logfile": "No se especificó archivo de log. Use: inav-analyze vuelo.bbl",
|
|
186
|
+
"error.compare_not_found": "Archivo de comparación no encontrado: {path}"
|
|
187
|
+
}
|