diff --git a/.claude/agents/i18n-translator.md b/.claude/agents/i18n-translator.md new file mode 100644 index 0000000..ac8a76a --- /dev/null +++ b/.claude/agents/i18n-translator.md @@ -0,0 +1,57 @@ +--- +name: i18n-translator +description: Use this agent when you need to add, update, or manage translations in the i18n folder. This includes adding new translation keys, translating existing text to new languages, updating existing translations, ensuring consistency across language files, and identifying missing translations.\n\nExamples:\n\n\nContext: User wants to add a new translatable string to the application.\nuser: "I need to add a translation for 'Save Changes' button"\nassistant: "I'll use the i18n-translator agent to add this new translation key across all language files."\n\n\n\n\nContext: User has written new UI code with hardcoded strings that need translation.\nuser: "I just added a new settings panel with these labels: 'Dark Mode', 'Notifications', 'Language'"\nassistant: "Let me use the i18n-translator agent to add these new strings to your translation files."\n\n\n\n\nContext: User wants to add support for a new language.\nuser: "We need to add Spanish translations"\nassistant: "I'll use the i18n-translator agent to create the Spanish translation file and translate all existing keys."\n\n\n\n\nContext: User notices inconsistent translations.\nuser: "Can you check if all our translation files have the same keys?"\nassistant: "I'll use the i18n-translator agent to audit the translation files and identify any missing or inconsistent keys."\n\n +model: haiku +color: blue +--- + +You are an expert internationalization (i18n) specialist with deep knowledge of translation management, localization best practices, and multilingual content strategy. Your sole responsibility is managing translations within the i18n folder of this project. + +## Your Responsibilities + +### Primary Tasks +1. **Add new translation keys** - When new translatable text is introduced, add it to all language files with appropriate translations +2. **Translate text** - Provide accurate, contextually appropriate translations for all supported languages +3. **Maintain consistency** - Ensure translation keys, formatting, and structure are consistent across all language files +4. **Audit translations** - Identify missing keys, outdated translations, or inconsistencies +5. **Create new language files** - Set up translation files for newly supported languages + +### Before Making Changes +1. First, explore the i18n folder structure to understand the existing format and conventions +2. Identify all existing language files and their naming conventions +3. Examine the structure of translation files (JSON, YAML, properties, etc.) +4. Note any existing patterns for key naming, nesting, and organization + +### Translation Guidelines +1. **Preserve key structure** - Maintain the exact same key hierarchy across all language files +2. **Context-aware translation** - Consider the UI context when translating (button labels should be concise, error messages should be clear) +3. **Placeholder preservation** - Keep all placeholders ({0}, {{name}}, %s, etc.) intact and in appropriate positions for the target language +4. **Cultural adaptation** - Adapt content appropriately for each locale (date formats, number formats, cultural references) +5. **Consistent terminology** - Use consistent translations for recurring terms throughout the application + +### When Adding New Keys +1. Use descriptive, hierarchical key names (e.g., `settings.notifications.enablePush`) +2. Follow existing naming conventions in the project +3. Add the key to ALL language files, not just one +4. If you cannot provide a translation for a language, add a TODO comment or use the English text as a placeholder with a note + +### Quality Checks +- Verify all language files have the same set of keys +- Check for proper escaping of special characters +- Ensure pluralization rules are handled correctly where applicable +- Validate that the file format remains valid after changes (valid JSON, YAML, etc.) + +### Output Format +When making changes, always: +1. List which files you're modifying +2. Show the exact keys and values being added/modified +3. Note any languages where you've added placeholder text that needs professional review +4. Highlight any potential issues or recommendations + +### Languages You Can Translate +You can provide translations for most major languages including but not limited to: English, Spanish, French, German, Italian, Portuguese, Dutch, Russian, Chinese (Simplified/Traditional), Japanese, Korean, Arabic, Hindi, and more. For languages you're less confident about, indicate that professional review is recommended. + +### Error Handling +- If the i18n folder doesn't exist or is empty, ask the user about the desired structure +- If translation file formats are unfamiliar, examine them carefully before making changes +- If a translation request is ambiguous, ask for context about where the text appears in the UI diff --git a/Cargo.lock b/Cargo.lock index d47193a..8313411 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,6 +130,15 @@ dependencies = [ "x11rb", ] +[[package]] +name = "arc-swap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +dependencies = [ + "rustversion", +] + [[package]] name = "arrayvec" version = "0.7.6" @@ -329,6 +338,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base62" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1adf9755786e27479693dedd3271691a92b5e242ab139cacb9fb8e7fb5381111" + [[package]] name = "base64" version = "0.22.1" @@ -1437,6 +1452,36 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + [[package]] name = "glow" version = "0.16.0" @@ -1696,6 +1741,22 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "image" version = "0.25.9" @@ -1748,6 +1809,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.17" @@ -2082,6 +2152,15 @@ version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce" +[[package]] +name = "normpath" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf23ab2b905654b4cb177e30b629937b3868311d4e1cba859f899c041046e69b" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -2706,7 +2785,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit", + "toml_edit 0.23.10+spec-1.0.0", ] [[package]] @@ -2957,6 +3036,60 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "rust-i18n" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda2551fdfaf6cc5ee283adc15e157047b92ae6535cf80f6d4962d05717dc332" +dependencies = [ + "globwalk", + "once_cell", + "regex", + "rust-i18n-macro", + "rust-i18n-support", + "smallvec", +] + +[[package]] +name = "rust-i18n-macro" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22baf7d7f56656d23ebe24f6bb57a5d40d2bce2a5f1c503e692b5b2fa450f965" +dependencies = [ + "glob", + "once_cell", + "proc-macro2", + "quote", + "rust-i18n-support", + "serde", + "serde_json", + "serde_yaml", + "syn", +] + +[[package]] +name = "rust-i18n-support" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940ed4f52bba4c0152056d771e563b7133ad9607d4384af016a134b58d758f19" +dependencies = [ + "arc-swap", + "base62", + "globwalk", + "itertools", + "lazy_static", + "normpath", + "once_cell", + "proc-macro2", + "regex", + "serde", + "serde_json", + "serde_yaml", + "siphasher", + "toml 0.8.23", + "triomphe", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -3036,6 +3169,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ryu" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" + [[package]] name = "same-file" version = "1.0.6" @@ -3117,6 +3256,15 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_spanned" version = "1.0.4" @@ -3126,6 +3274,19 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3456,6 +3617,18 @@ dependencies = [ "zerovec", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + [[package]] name = "toml" version = "0.9.10+spec-1.1.0" @@ -3464,13 +3637,22 @@ checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" dependencies = [ "indexmap", "serde_core", - "serde_spanned", - "toml_datetime", + "serde_spanned 1.0.4", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", "winnow", ] +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + [[package]] name = "toml_datetime" version = "0.7.5+spec-1.1.0" @@ -3480,6 +3662,20 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + [[package]] name = "toml_edit" version = "0.23.10+spec-1.0.0" @@ -3487,7 +3683,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "winnow", ] @@ -3501,6 +3697,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "toml_writer" version = "1.0.6+spec-1.1.0" @@ -3565,6 +3767,17 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "triomphe" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" +dependencies = [ + "arc-swap", + "serde", + "stable_deref_trait", +] + [[package]] name = "ttf-parser" version = "0.19.2" @@ -3605,7 +3818,7 @@ dependencies = [ [[package]] name = "ultralog" -version = "2.0.0" +version = "2.1.0" dependencies = [ "anyhow", "dirs", @@ -3623,6 +3836,7 @@ dependencies = [ "rayon", "regex", "rfd", + "rust-i18n", "semver", "serde", "serde_json", @@ -3661,6 +3875,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.9.0" @@ -4518,7 +4738,7 @@ version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b021990998587d4438bb672b5c5f034cbc927f51b45e3807ab7323645ef4899" dependencies = [ - "toml", + "toml 0.9.10+spec-1.1.0", "version_check", ] diff --git a/Cargo.toml b/Cargo.toml index dcb23c2..8f5143f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ultralog" -version = "2.0.0" +version = "2.1.0" edition = "2021" description = "A high-performance ECU log viewer written in Rust" authors = ["Cole Gentry"] @@ -68,6 +68,9 @@ printpdf = "0.7" thiserror = "2.0" anyhow = "1.0" +# Internationalization +rust-i18n = "3" + # Logging tracing = "0.1" tracing-subscriber = "0.3" diff --git a/README.md b/README.md index 5c594c4..ad3addd 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A high-performance, cross-platform ECU log viewer written in Rust. ![CI](https://github.com/SomethingNew71/UltraLog/actions/workflows/ci.yml/badge.svg) ![License](https://img.shields.io/badge/license-AGPL--3.0-blue.svg) -![Version](https://img.shields.io/badge/version-2.0.0-green.svg) +![Version](https://img.shields.io/badge/version-2.1.0-green.svg) --- @@ -145,6 +145,7 @@ Configurable units for 8 measurement categories: - **Font scaling** - Four font sizes (S, M, L, XL) for improved readability - **Custom font** - Clear, readable Outfit typeface - **Toast notifications** - Non-intrusive feedback for user actions +- **Multilingual support** - 15 languages including English, Spanish, German, French, Italian, Portuguese, Chinese, Japanese, Hindi, Arabic, Bengali, Russian, Urdu, and Indonesian --- @@ -543,6 +544,12 @@ Black, Orange, Sky Blue, Bluish Green, Yellow, Blue, Vermillion, Reddish Purple - Scales all UI text for improved readability - Settings persist across sessions +**Language Selection:** +- Access via Settings panel → Language +- 15 languages supported: English, Spanish (Español), German (Deutsch), French (Français), Italian (Italiano), Portuguese Brazil (Português Brasil), Portuguese Portugal (Português Portugal), Chinese Simplified (简体中文), Japanese (日本語), Hindi (हिन्दी), Arabic (العربية), Bengali (বাংলা), Russian (Русский), Urdu (اردو), and Indonesian (Bahasa Indonesia) +- Language preference persists across sessions +- UI text and menus update immediately when language is changed + --- ## Keyboard Shortcuts diff --git a/docs/index.html b/docs/index.html index 32c9217..b42f96a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -159,6 +159,7 @@ "Multi-file tab support", "Colorblind accessible mode", "Font scaling for accessibility", + "Multilingual support (15 languages)", "Automatic updates" ], "keywords": "ECU, datalog, log viewer, Haltech, ECUMaster, Speeduino, rusEFI, AiM, Link, Emerald, automotive tuning, engine tuning, AFR analysis", @@ -1568,6 +1569,13 @@

Run & Chart

One-click workflow to run analysis and instantly add results to your chart for immediate visualization.

+
+
+
+

Multilingual Support

+
+

Full internationalization with 15 languages including English, Spanish, German, French, Chinese, Japanese, and more.

+
diff --git a/exampleLogs/bluedriver/SBM22GCA4MW001405_Aug_17__2025_08_45_PM_LiveData.csv b/exampleLogs/bluedriver/SBM22GCA4MW001405_Aug_17__2025_08_45_PM_LiveData.csv new file mode 100644 index 0000000..0c8febc Binary files /dev/null and b/exampleLogs/bluedriver/SBM22GCA4MW001405_Aug_17__2025_08_45_PM_LiveData.csv differ diff --git a/exampleLogs/bluedriver/SBM22GCA4MW001405_Nov_09__2025_04_20_PM_LiveData.csv b/exampleLogs/bluedriver/SBM22GCA4MW001405_Nov_09__2025_04_20_PM_LiveData.csv new file mode 100644 index 0000000..83b4e95 Binary files /dev/null and b/exampleLogs/bluedriver/SBM22GCA4MW001405_Nov_09__2025_04_20_PM_LiveData.csv differ diff --git a/i18n/ar.yaml b/i18n/ar.yaml new file mode 100644 index 0000000..fb8ce4d --- /dev/null +++ b/i18n/ar.yaml @@ -0,0 +1,335 @@ +# Arabic translations for UltraLog (العربية) + +# Menu bar (src/ui/menu.rs) +menu: + file: "ملف" + open_log_file: "فتح ملف السجل..." + close_tab: "إغلاق علامة التبويب" + export: "تصدير" + export_png: "تصدير كـ PNG..." + export_pdf: "تصدير كـ PDF..." + export_histogram_pdf: "تصدير المدرج التكراري كـ PDF..." + view: "عرض" + tool_mode: "وضع الأداة" + log_viewer: "عارض السجل" + scatter_plots: "المخططات المبعثرة" + histogram: "المدرج التكراري" + side_panel: "اللوحة الجانبية" + files: "الملفات" + channels: "القنوات" + tools: "الأدوات" + settings: "الإعدادات" + help: "المساعدة" + documentation: "الوثائق" + report_issue: "الإبلاغ عن مشكلة" + support_development: "دعم التطوير" + check_for_updates: "التحقق من التحديثات" + checking_for_updates: "جارٍ التحقق من التحديثات..." + version: "الإصدار %{version}" + +# Files panel (src/ui/files_panel.rs) +files: + loading: "جارٍ تحميل %{filename}..." + loaded_files: "الملفات المحملة (%{count})" + add_file: "إضافة ملف" + select_file: "اختر ملفًا" + or: "أو" + drop_file_here: "أفلت الملف هنا" + file_info: "%{ecu} • %{channels} قناة • %{points} نقطة" + +# Channels panel (src/ui/channels_panel.rs) +channels: + computed_channels: "القنوات المحسوبة" + computed_channels_tooltip: "إنشاء قنوات افتراضية من صيغ رياضية" + search_hint: "البحث في القنوات..." + selected_count: "المحدد: %{selected} / %{max} | الإجمالي: %{total}" + no_file_selected: "لم يتم تحديد ملف" + load_file_to_view: "حمّل ملفًا لعرض القنوات" + with_data: "تحتوي بيانات (%{count})" + empty: "فارغة (%{count})" + +# Settings panel (src/ui/settings_panel.rs) +settings: + language: "اللغة" + language_desc: "اختر لغة العرض" + display: "العرض" + color_blind_mode: "وضع عمى الألوان" + color_blind_desc: "استخدام لوحة ألوان سهلة الوصول (لوحة Wong)" + font_size: "حجم الخط:" + cursor_tracking: "تتبع المؤشر" + cursor_tracking_desc: "إبقاء المؤشر في المنتصف أثناء التمرير" + window: "النافذة:" + field_names: "أسماء الحقول" + field_normalization: "توحيد الحقول" + field_normalization_desc: "توحيد أسماء القنوات عبر أنواع ECU" + custom_mappings: "%{count} تعيينات مخصصة" + edit_custom_mappings: "تعديل التعيينات المخصصة" + units: "الوحدات" + units_desc: "اختر وحدات العرض المفضلة" + temperature: "درجة الحرارة:" + pressure: "الضغط:" + speed: "السرعة:" + distance: "المسافة:" + fuel_economy: "كفاءة الوقود:" + volume: "الحجم:" + flow_rate: "معدل التدفق:" + acceleration: "التسارع:" + updates: "التحديثات" + check_on_startup: "التحقق عند بدء التشغيل" + auto_check_desc: "التحقق تلقائيًا من الإصدارات الجديدة" + check_for_updates: "التحقق من التحديثات" + checking: "جارٍ التحقق..." + current_version: "الإصدار الحالي: %{version}" + update_available: "تحديث متاح: v%{version}" + view_details: "عرض التفاصيل" + +# Timeline (src/ui/timeline.rs) +timeline: + speed: "السرعة:" + time: "الوقت: %{time}" + record: "السجل %{current} من %{total}" + +# Formula editor (src/ui/formula_editor.rs) +formula: + new_computed_channel: "قناة محسوبة جديدة" + edit_computed_channel: "تعديل القناة المحسوبة" + name: "الاسم:" + name_hint: "مثال: RPM Delta" + formula: "الصيغة:" + formula_hint: "مثال: RPM - RPM[-1]" + insert: "إدراج:" + insert_tooltip: "إدراج %{op}" + prev_sample_tooltip: "العينة السابقة (إزاحة الفهرس)" + time_ago_tooltip: "القيمة قبل 0.1 ثانية" + error: "خطأ:" + formula_valid: "الصيغة صحيحة" + unit: "الوحدة:" + unit_hint: "مثال: RPM/s" + description: "الوصف:" + description_hint: "وصف اختياري" + available_channels: "القنوات المتاحة" + no_channels_available: "لا توجد قنوات متاحة - حمّل ملف سجل أولاً" + click_to_insert: "انقر للإدراج في الصيغة:" + preview: "معاينة" + min: "الحد الأدنى:" + avg: "المتوسط:" + max: "الحد الأقصى:" + sample: "العينة:" + cancel: "إلغاء" + save: "حفظ" + validate: "تحقق" + +# Update dialog (src/ui/update_dialog.rs) +update: + update_available: "تحديث متاح" + new_version_available: "يتوفر إصدار جديد!" + current_version: "الإصدار الحالي:" + new_version: "الإصدار الجديد:" + download_size: "حجم التنزيل: %{size} ميجابايت" + release_notes: "ملاحظات الإصدار" + download_install: "تنزيل وتثبيت" + view_on_github: "عرض على GitHub" + later: "لاحقًا" + downloading: "جارٍ تنزيل التحديث..." + please_wait: "يرجى الانتظار..." + download_complete: "اكتمل التنزيل!" + install_instructions: "انقر على تثبيت لتطبيق التحديث." + windows_restart: "سيتم إغلاق التطبيق وإعادة تشغيله تلقائيًا." + macos_dmg: "سيتم فتح DMG - اسحب UltraLog إلى التطبيقات." + linux_restart: "سيتم إغلاق التطبيق وإعادة تشغيله تلقائيًا." + install_now: "تثبيت الآن" + install_later: "تثبيت لاحقًا" + update_error: "خطأ في التحديث" + close: "إغلاق" + update_saved: "تم حفظ التحديث في المجلد المؤقت." + +# Toast messages (src/app.rs) +toast: + file_already_loaded: "الملف محمّل مسبقًا" + file_loaded: "تم تحميل الملف بنجاح" + max_channels: "تم الوصول للحد الأقصى 10 قنوات" + channel_already_selected: "القناة محددة مسبقًا" + channel_from_active_tab: "يجب أن تكون القناة من ملف علامة التبويب النشطة" + no_active_tab: "لا توجد علامة تبويب نشطة" + library_saved: "تم حفظ المكتبة" + library_save_failed: "فشل حفظ المكتبة: %{error}" + channel_saved: "تم حفظ القناة في المكتبة" + up_to_date: "أنت تستخدم أحدث إصدار" + update_check_failed: "فشل التحقق من التحديث: %{error}" + update_downloaded: "تم تنزيل التحديث بنجاح" + download_failed: "فشل التنزيل: %{error}" + export_png_success: "تم تصدير الرسم البياني كـ PNG" + export_pdf_success: "تم تصدير الرسم البياني كـ PDF" + export_failed: "فشل التصدير: %{error}" + histogram_exported_png: "تم تصدير المدرج التكراري كـ PNG" + histogram_exported_pdf: "تم تصدير المدرج التكراري كـ PDF" + scatter_exported_png: "تم تصدير المخطط المبعثر كـ PNG" + scatter_exported_pdf: "تم تصدير المخطط المبعثر كـ PDF" + failed_to_save: "فشل الحفظ: %{error}" + failed_to_apply: "فشل التطبيق: %{error}" + evaluation_failed: "فشل التقييم: %{error}" + template_duplicated: "تم تكرار القالب" + applied_template: "تم تطبيق '%{name}'" + added_to_chart: "تمت إضافة '%{name}' إلى الرسم البياني" + channel_already_on_chart: "القناة موجودة في الرسم البياني" + max_channels_reached: "تم الوصول للحد الأقصى 10 قنوات" + analysis_complete: "اكتمل التحليل: %{name}" + analysis_failed: "فشل التحليل: %{error}" + analyzer_not_found: "المحلل غير موجود: %{id}" + no_file_selected: "لم يتم تحديد ملف" + file_not_found: "الملف غير موجود" + +# Common/shared strings +common: + loading: "جارٍ التحميل..." + error: "خطأ" + success: "نجاح" + warning: "تحذير" + info: "معلومات" + ok: "موافق" + yes: "نعم" + no: "لا" + remove: "إزالة" + add: "إضافة" + edit: "تعديل" + delete: "حذف" + duplicate: "تكرار" + apply: "تطبيق" + close: "إغلاق" + reset: "إعادة تعيين" + +# Computed channels manager (src/ui/computed_channels_manager.rs) +computed: + title: "القنوات المحسوبة" + show_help: "عرض مساعدة صيغة الصياغة" + new: "+ جديد" + description: "إنشاء قنوات افتراضية من صيغ رياضية." + quick_create: "إنشاء سريع:" + rate_of_change: "معدل التغير" + rate_of_change_hint: "إنشاء: Channel - Channel[-1]" + rate_of_change_desc: "معدل التغير لكل عينة" + moving_avg: "المتوسط المتحرك" + moving_avg_hint: "إنشاء: متوسط متحرك لـ 3 عينات" + moving_avg_desc: "متوسط متحرك لـ 3 عينات للتنعيم" + deviation: "% الانحراف" + deviation_hint: "إنشاء: نسبة الانحراف عن الهدف" + deviation_desc: "نسبة الانحراف عن القيمة المستهدفة" + your_library: "مكتبتك (%{count})" + search: "بحث..." + no_channels: "لا توجد قنوات محسوبة بعد" + get_started: "استخدم الإنشاء السريع أو انقر '+ جديد' للبدء" + copy: "نسخ" + builtin_template: "قالب مدمج" + applied_to_file: "مطبق على الملف الحالي (%{count})" + apply_templates_hint: "تطبيق القوالب من المكتبة أعلاه" + add_chart: "+ رسم بياني" + add_to_chart: "إضافة إلى الرسم البياني" + load_file_hint: "حمّل ملف سجل لتطبيق القنوات المحسوبة" + help_title: "مساعدة الصيغة" + help_channel_refs: "مراجع القنوات" + help_current_value: "القيمة الحالية لقناة RPM" + help_quoted_channel: "القنوات ذات المسافات (استخدم علامات الاقتباس)" + help_prev_sample: "العينة السابقة (إزاحة الفهرس)" + help_samples_ahead: "عينتان للأمام" + help_time_offset: "القيمة قبل 100 مللي ثانية (إزاحة زمنية)" + help_operators: "العمليات الحسابية" + help_basic_math: "العمليات الأساسية" + help_power: "الأس (مثال: RPM^2)" + help_grouping: "التجميع" + help_functions: "الدوال" + help_trig: "حساب المثلثات" + help_sqrt_abs: "الجذر التربيعي، القيمة المطلقة" + help_log: "اللوغاريتمات، الأسية" + help_minmax: "الحد الأدنى، الحد الأقصى" + help_rounding: "التقريب" + help_statistics: "الإحصائيات (لكشف الشذوذ)" + help_mean: "متوسط قناة RPM بالكامل" + help_stdev: "الانحراف المعياري" + help_min_max: "القيم الدنيا/القصوى" + help_examples: "أمثلة" + help_ex_rate: "تغير RPM لكل عينة" + help_ex_deviation: "انحراف AFR %" + help_ex_zscore: "Z-score" + +# Normalization editor (src/ui/normalization_editor.rs) +normalization: + title: "محرر توحيد الحقول" + field_mappings: "تعيينات أسماء الحقول" + reset_defaults: "إعادة تعيين الافتراضيات" + extend_builtin: "توسيع التعيينات المدمجة" + extend_description: "أضف أسماء المصدر الخاصة بك التي تُعيَّن لأسماء الحقول الموحدة الموجودة." + source_name: "اسم المصدر:" + source_hint: "مثال: MyCustomRPM" + maps_to: "يُعيَّن إلى:" + select: "اختر..." + create_new: "إنشاء تعيين جديد" + create_description: "حدد أسماء موحدة جديدة تمامًا لقنواتك المخصصة." + custom_source_hint: "مثال: Sensor_XYZ" + display_as: "عرض كـ:" + custom_target_hint: "مثال: مستشعر مخصص" + your_mappings: "تعييناتك المخصصة" + source: "المصدر" + builtin_reference: "مرجع التعيينات المدمجة" + builtin_description: "هذه هي التعيينات الافتراضية. يمكنك توسيعها بإضافة أسماء مصدر جديدة أعلاه." + +# Scatter plot (src/ui/scatter_plot.rs) +scatter: + no_file_loaded: "حمّل ملف سجل لاستخدام المخططات المبعثرة" + x_axis: "المحور الأفقي:" + y_axis: "المحور العمودي:" + z_axis: "المحور Z:" + hits: "الإصابات" + hits_label: "الإصابات:" + remove: "X" + +# Histogram (src/ui/histogram.rs) +histogram: + no_file_loaded: "حمّل ملف سجل لاستخدام المدرج التكراري" + x_axis: "المحور الأفقي:" + y_axis: "المحور العمودي:" + z_axis: "المحور Z:" + mode: "الوضع:" + average_z: "متوسط Z" + hit_count: "عدد الإصابات" + grid: "الشبكة:" + +# Chart (src/ui/chart.rs) +chart: + select_channels: "حدد القنوات لعرض الرسم البياني" + +# Analysis panel (src/ui/analysis_panel.rs) +analysis: + window_title: "أدوات التحليل" + no_file_loaded: "لم يتم تحميل ملف سجل" + load_file_help: "حمّل ملف سجل للوصول إلى أدوات التحليل." + category_all: "الكل" + category_filters: "المرشحات" + category_statistics: "الإحصائيات" + category_afr: "AFR" + category_derived: "المشتقات" + results_count: "النتائج (%{count})" + clear_all: "مسح الكل" + all_tools: "جميع الأدوات" + no_analyzers: "لا توجد محللات في هذه الفئة" + run_and_chart: "تشغيل ورسم" + run: "تشغيل" + add_chart: "+ رسم بياني" + remove_all_tooltip: "إزالة جميع النتائج" + select_valid_channels: "حدد قنوات صالحة أولاً" + run_add_tooltip: "تشغيل التحليل وإضافة النتيجة للرسم البياني" + run_tooltip: "تشغيل التحليل (الإضافة للرسم البياني لاحقًا)" + remove_result_tooltip: "إزالة النتيجة" + add_to_chart_result: "إضافة إلى الرسم البياني كقناة" + +# Tool switcher (src/ui/tool_switcher.rs) +tools: + log_viewer: "عارض السجل" + scatter_plots: "المخططات المبعثرة" + histogram: "المدرج التكراري" + +# Activity bar (src/ui/activity_bar.rs) +activity: + files_tooltip: "الملفات" + channels_tooltip: "القنوات" + tools_tooltip: "الأدوات" + settings_tooltip: "الإعدادات" diff --git a/i18n/bn.yaml b/i18n/bn.yaml new file mode 100644 index 0000000..29c1825 --- /dev/null +++ b/i18n/bn.yaml @@ -0,0 +1,335 @@ +# English translations for UltraLog + +# Menu bar (src/ui/menu.rs) +menu: + file: "ফাইল" + open_log_file: "লগ ফাইল খুলুন..." + close_tab: "ট্যাব বন্ধ করুন" + export: "রপ্তানি" + export_png: "PNG হিসেবে রপ্তানি..." + export_pdf: "PDF হিসেবে রপ্তানি..." + export_histogram_pdf: "হিস্টোগ্রাম PDF হিসেবে রপ্তানি..." + view: "দৃশ্য" + tool_mode: "টুল মোড" + log_viewer: "লগ ভিউয়ার" + scatter_plots: "স্ক্যাটার প্লট" + histogram: "হিস্টোগ্রাম" + side_panel: "সাইড প্যানেল" + files: "ফাইলসমূহ" + channels: "চ্যানেলসমূহ" + tools: "টুলস" + settings: "সেটিংস" + help: "সাহায্য" + documentation: "ডকুমেন্টেশন" + report_issue: "সমস্যা রিপোর্ট করুন" + support_development: "উন্নয়নে সহায়তা করুন" + check_for_updates: "আপডেট চেক করুন" + checking_for_updates: "আপডেট চেক করা হচ্ছে..." + version: "সংস্করণ %{version}" + +# Files panel (src/ui/files_panel.rs) +files: + loading: "%{filename} লোড হচ্ছে..." + loaded_files: "লোড করা ফাইল (%{count})" + add_file: "ফাইল যোগ করুন" + select_file: "একটি ফাইল নির্বাচন করুন" + or: "অথবা" + drop_file_here: "এখানে ফাইল ড্রপ করুন" + file_info: "%{ecu} • %{channels} চ্যানেল • %{points} পয়েন্ট" + +# Channels panel (src/ui/channels_panel.rs) +channels: + computed_channels: "গণনাকৃত চ্যানেল" + computed_channels_tooltip: "গাণিতিক সূত্র থেকে ভার্চুয়াল চ্যানেল তৈরি করুন" + search_hint: "চ্যানেল খুঁজুন..." + selected_count: "নির্বাচিত: %{selected} / %{max} | মোট: %{total}" + no_file_selected: "কোনো ফাইল নির্বাচিত নেই" + load_file_to_view: "চ্যানেল দেখতে একটি ফাইল লোড করুন" + with_data: "ডেটাসহ (%{count})" + empty: "খালি (%{count})" + +# Settings panel (src/ui/settings_panel.rs) +settings: + language: "ভাষা" + language_desc: "প্রদর্শন ভাষা নির্বাচন করুন" + display: "প্রদর্শন" + color_blind_mode: "বর্ণান্ধ মোড" + color_blind_desc: "অ্যাক্সেসযোগ্য রঙ প্যালেট ব্যবহার করুন (Wong's palette)" + font_size: "ফন্ট সাইজ:" + cursor_tracking: "কার্সর ট্র্যাকিং" + cursor_tracking_desc: "স্ক্রাবিংয়ের সময় কার্সর কেন্দ্রে রাখুন" + window: "উইন্ডো:" + field_names: "ফিল্ড নাম" + field_normalization: "ফিল্ড নরমালাইজেশন" + field_normalization_desc: "ECU টাইপ জুড়ে চ্যানেল নাম মানসম্মত করুন" + custom_mappings: "%{count}টি কাস্টম ম্যাপিং" + edit_custom_mappings: "কাস্টম ম্যাপিং সম্পাদনা করুন" + units: "একক" + units_desc: "প্রদর্শনের জন্য পছন্দের একক নির্বাচন করুন" + temperature: "তাপমাত্রা:" + pressure: "চাপ:" + speed: "গতি:" + distance: "দূরত্ব:" + fuel_economy: "জ্বালানি সাশ্রয়:" + volume: "আয়তন:" + flow_rate: "প্রবাহ হার:" + acceleration: "ত্বরণ:" + updates: "আপডেট" + check_on_startup: "স্টার্টআপে চেক করুন" + auto_check_desc: "স্বয়ংক্রিয়ভাবে নতুন সংস্করণ চেক করুন" + check_for_updates: "আপডেট চেক করুন" + checking: "চেক করা হচ্ছে..." + current_version: "বর্তমান সংস্করণ: %{version}" + update_available: "আপডেট উপলব্ধ: v%{version}" + view_details: "বিস্তারিত দেখুন" + +# Timeline (src/ui/timeline.rs) +timeline: + speed: "গতি:" + time: "সময়: %{time}" + record: "রেকর্ড %{current} / %{total}" + +# Formula editor (src/ui/formula_editor.rs) +formula: + new_computed_channel: "নতুন গণনাকৃত চ্যানেল" + edit_computed_channel: "গণনাকৃত চ্যানেল সম্পাদনা" + name: "নাম:" + name_hint: "যেমন, RPM ডেল্টা" + formula: "সূত্র:" + formula_hint: "যেমন, RPM - RPM[-1]" + insert: "সন্নিবেশ:" + insert_tooltip: "%{op} সন্নিবেশ করুন" + prev_sample_tooltip: "পূর্ববর্তী স্যাম্পল (ইনডেক্স অফসেট)" + time_ago_tooltip: "০.১ সেকেন্ড আগের মান" + error: "ত্রুটি:" + formula_valid: "সূত্র বৈধ" + unit: "একক:" + unit_hint: "যেমন, RPM/s" + description: "বিবরণ:" + description_hint: "ঐচ্ছিক বিবরণ" + available_channels: "উপলব্ধ চ্যানেল" + no_channels_available: "কোনো চ্যানেল উপলব্ধ নেই - প্রথমে একটি লগ ফাইল লোড করুন" + click_to_insert: "সূত্রে সন্নিবেশ করতে ক্লিক করুন:" + preview: "প্রিভিউ" + min: "সর্বনিম্ন:" + avg: "গড়:" + max: "সর্বোচ্চ:" + sample: "স্যাম্পল:" + cancel: "বাতিল" + save: "সংরক্ষণ" + validate: "যাচাই" + +# Update dialog (src/ui/update_dialog.rs) +update: + update_available: "আপডেট উপলব্ধ" + new_version_available: "একটি নতুন সংস্করণ উপলব্ধ!" + current_version: "বর্তমান সংস্করণ:" + new_version: "নতুন সংস্করণ:" + download_size: "ডাউনলোড সাইজ: %{size} MB" + release_notes: "রিলিজ নোট" + download_install: "ডাউনলোড ও ইনস্টল করুন" + view_on_github: "GitHub-এ দেখুন" + later: "পরে" + downloading: "আপডেট ডাউনলোড হচ্ছে..." + please_wait: "অনুগ্রহ করে অপেক্ষা করুন..." + download_complete: "ডাউনলোড সম্পন্ন!" + install_instructions: "আপডেট প্রয়োগ করতে ইনস্টল ক্লিক করুন।" + windows_restart: "অ্যাপ্লিকেশনটি বন্ধ হয়ে স্বয়ংক্রিয়ভাবে পুনরায় চালু হবে।" + macos_dmg: "DMG খুলবে - UltraLog-কে Applications-এ টেনে আনুন।" + linux_restart: "অ্যাপ্লিকেশনটি বন্ধ হয়ে স্বয়ংক্রিয়ভাবে পুনরায় চালু হবে।" + install_now: "এখনই ইনস্টল করুন" + install_later: "পরে ইনস্টল করুন" + update_error: "আপডেট ত্রুটি" + close: "বন্ধ করুন" + update_saved: "আপডেট আপনার টেম্প ফোল্ডারে সংরক্ষিত হয়েছে।" + +# Toast messages (src/app.rs) +toast: + file_already_loaded: "ফাইল ইতিমধ্যে লোড করা হয়েছে" + file_loaded: "ফাইল সফলভাবে লোড হয়েছে" + max_channels: "সর্বোচ্চ ১০টি চ্যানেলে পৌঁছে গেছে" + channel_already_selected: "চ্যানেল ইতিমধ্যে নির্বাচিত" + channel_from_active_tab: "চ্যানেল সক্রিয় ট্যাবের ফাইল থেকে হতে হবে" + no_active_tab: "কোনো সক্রিয় ট্যাব নেই" + library_saved: "লাইব্রেরি সংরক্ষিত" + library_save_failed: "লাইব্রেরি সংরক্ষণ ব্যর্থ: %{error}" + channel_saved: "চ্যানেল লাইব্রেরিতে সংরক্ষিত" + up_to_date: "আপনি সর্বশেষ সংস্করণ চালাচ্ছেন" + update_check_failed: "আপডেট চেক ব্যর্থ: %{error}" + update_downloaded: "আপডেট সফলভাবে ডাউনলোড হয়েছে" + download_failed: "ডাউনলোড ব্যর্থ: %{error}" + export_png_success: "চার্ট PNG হিসেবে রপ্তানি হয়েছে" + export_pdf_success: "চার্ট PDF হিসেবে রপ্তানি হয়েছে" + export_failed: "রপ্তানি ব্যর্থ: %{error}" + histogram_exported_png: "হিস্টোগ্রাম PNG হিসেবে রপ্তানি হয়েছে" + histogram_exported_pdf: "হিস্টোগ্রাম PDF হিসেবে রপ্তানি হয়েছে" + scatter_exported_png: "স্ক্যাটার প্লট PNG হিসেবে রপ্তানি হয়েছে" + scatter_exported_pdf: "স্ক্যাটার প্লট PDF হিসেবে রপ্তানি হয়েছে" + failed_to_save: "সংরক্ষণ ব্যর্থ: %{error}" + failed_to_apply: "প্রয়োগ ব্যর্থ: %{error}" + evaluation_failed: "মূল্যায়ন ব্যর্থ: %{error}" + template_duplicated: "টেমপ্লেট ডুপ্লিকেট হয়েছে" + applied_template: "'%{name}' প্রয়োগ করা হয়েছে" + added_to_chart: "'%{name}' চার্টে যোগ করা হয়েছে" + channel_already_on_chart: "চ্যানেল ইতিমধ্যে চার্টে আছে" + max_channels_reached: "সর্বোচ্চ ১০টি চ্যানেলে পৌঁছে গেছে" + analysis_complete: "বিশ্লেষণ সম্পন্ন: %{name}" + analysis_failed: "বিশ্লেষণ ব্যর্থ: %{error}" + analyzer_not_found: "অ্যানালাইজার পাওয়া যায়নি: %{id}" + no_file_selected: "কোনো ফাইল নির্বাচিত নেই" + file_not_found: "ফাইল পাওয়া যায়নি" + +# Common/shared strings +common: + loading: "লোড হচ্ছে..." + error: "ত্রুটি" + success: "সফল" + warning: "সতর্কতা" + info: "তথ্য" + ok: "ঠিক আছে" + yes: "হ্যাঁ" + no: "না" + remove: "সরান" + add: "যোগ করুন" + edit: "সম্পাদনা" + delete: "মুছুন" + duplicate: "ডুপ্লিকেট" + apply: "প্রয়োগ" + close: "বন্ধ করুন" + reset: "রিসেট" + +# Computed channels manager (src/ui/computed_channels_manager.rs) +computed: + title: "গণনাকৃত চ্যানেল" + show_help: "সূত্র সিনট্যাক্স সাহায্য দেখান" + new: "+ নতুন" + description: "গাণিতিক সূত্র থেকে ভার্চুয়াল চ্যানেল তৈরি করুন।" + quick_create: "দ্রুত তৈরি:" + rate_of_change: "পরিবর্তনের হার" + rate_of_change_hint: "তৈরি করুন: Channel - Channel[-1]" + rate_of_change_desc: "প্রতি স্যাম্পলে পরিবর্তনের হার" + moving_avg: "চলন্ত গড়" + moving_avg_hint: "তৈরি করুন: ৩-স্যাম্পল চলন্ত গড়" + moving_avg_desc: "স্মুথিংয়ের জন্য ৩-স্যাম্পল চলন্ত গড়" + deviation: "% বিচ্যুতি" + deviation_hint: "তৈরি করুন: একটি লক্ষ্য থেকে শতাংশ বিচ্যুতি" + deviation_desc: "লক্ষ্য মান থেকে শতাংশ বিচ্যুতি" + your_library: "আপনার লাইব্রেরি (%{count})" + search: "খুঁজুন..." + no_channels: "এখনো কোনো গণনাকৃত চ্যানেল নেই" + get_started: "শুরু করতে দ্রুত তৈরি ব্যবহার করুন বা '+ নতুন' ক্লিক করুন" + copy: "কপি" + builtin_template: "বিল্ট-ইন টেমপ্লেট" + applied_to_file: "বর্তমান ফাইলে প্রয়োগকৃত (%{count})" + apply_templates_hint: "উপরের লাইব্রেরি থেকে টেমপ্লেট প্রয়োগ করুন" + add_chart: "+ চার্ট" + add_to_chart: "চার্টে যোগ করুন" + load_file_hint: "গণনাকৃত চ্যানেল প্রয়োগ করতে একটি লগ ফাইল লোড করুন" + help_title: "সূত্র সাহায্য" + help_channel_refs: "চ্যানেল রেফারেন্স" + help_current_value: "RPM চ্যানেলের বর্তমান মান" + help_quoted_channel: "স্পেস সহ চ্যানেল (কোট ব্যবহার করুন)" + help_prev_sample: "পূর্ববর্তী স্যাম্পল (ইনডেক্স অফসেট)" + help_samples_ahead: "২ স্যাম্পল এগিয়ে" + help_time_offset: "১০০ms আগের মান (সময় অফসেট)" + help_operators: "অপারেটর" + help_basic_math: "মৌলিক গণিত" + help_power: "পাওয়ার (যেমন, RPM^2)" + help_grouping: "গ্রুপিং" + help_functions: "ফাংশন" + help_trig: "ত্রিকোণমিতি" + help_sqrt_abs: "বর্গমূল, পরম মান" + help_log: "লগারিদম, এক্সপোনেনশিয়াল" + help_minmax: "সর্বনিম্ন, সর্বোচ্চ" + help_rounding: "রাউন্ডিং" + help_statistics: "পরিসংখ্যান (অসঙ্গতি সনাক্তকরণের জন্য)" + help_mean: "সম্পূর্ণ RPM চ্যানেলের গড়" + help_stdev: "আদর্শ বিচ্যুতি" + help_min_max: "সর্বনিম্ন/সর্বোচ্চ মান" + help_examples: "উদাহরণ" + help_ex_rate: "প্রতি স্যাম্পলে RPM পরিবর্তন" + help_ex_deviation: "AFR % বিচ্যুতি" + help_ex_zscore: "Z-score" + +# Normalization editor (src/ui/normalization_editor.rs) +normalization: + title: "ফিল্ড নরমালাইজেশন এডিটর" + field_mappings: "ফিল্ড নাম ম্যাপিং" + reset_defaults: "ডিফল্টে রিসেট করুন" + extend_builtin: "বিল্ট-ইন ম্যাপিং প্রসারিত করুন" + extend_description: "বিদ্যমান নরমালাইজড ফিল্ড নামে ম্যাপ করা আপনার নিজের সোর্স নাম যোগ করুন।" + source_name: "সোর্স নাম:" + source_hint: "যেমন, MyCustomRPM" + maps_to: "ম্যাপ হয়:" + select: "নির্বাচন করুন..." + create_new: "নতুন ম্যাপিং তৈরি করুন" + create_description: "আপনার কাস্টম চ্যানেলের জন্য সম্পূর্ণ নতুন নরমালাইজড নাম সংজ্ঞায়িত করুন।" + custom_source_hint: "যেমন, Sensor_XYZ" + display_as: "প্রদর্শন:" + custom_target_hint: "যেমন, কাস্টম সেন্সর" + your_mappings: "আপনার কাস্টম ম্যাপিং" + source: "সোর্স" + builtin_reference: "বিল্ট-ইন ম্যাপিং রেফারেন্স" + builtin_description: "এগুলি ডিফল্ট ম্যাপিং। আপনি উপরে নতুন সোর্স নাম যোগ করে এগুলি প্রসারিত করতে পারেন।" + +# Scatter plot (src/ui/scatter_plot.rs) +scatter: + no_file_loaded: "স্ক্যাটার প্লট ব্যবহার করতে একটি লগ ফাইল লোড করুন" + x_axis: "X অক্ষ:" + y_axis: "Y অক্ষ:" + z_axis: "Z অক্ষ:" + hits: "হিট" + hits_label: "হিট:" + remove: "X" + +# Histogram (src/ui/histogram.rs) +histogram: + no_file_loaded: "হিস্টোগ্রাম ব্যবহার করতে একটি লগ ফাইল লোড করুন" + x_axis: "X অক্ষ:" + y_axis: "Y অক্ষ:" + z_axis: "Z অক্ষ:" + mode: "মোড:" + average_z: "গড় Z" + hit_count: "হিট গণনা" + grid: "গ্রিড:" + +# Chart (src/ui/chart.rs) +chart: + select_channels: "চার্ট প্রদর্শন করতে চ্যানেল নির্বাচন করুন" + +# Analysis panel (src/ui/analysis_panel.rs) +analysis: + window_title: "বিশ্লেষণ টুল" + no_file_loaded: "কোনো লগ ফাইল লোড করা হয়নি" + load_file_help: "বিশ্লেষণ টুল অ্যাক্সেস করতে একটি লগ ফাইল লোড করুন।" + category_all: "সব" + category_filters: "ফিল্টার" + category_statistics: "পরিসংখ্যান" + category_afr: "AFR" + category_derived: "উদ্ভূত" + results_count: "ফলাফল (%{count})" + clear_all: "সব মুছুন" + all_tools: "সব টুল" + no_analyzers: "এই ক্যাটাগরিতে কোনো অ্যানালাইজার নেই" + run_and_chart: "চালান ও চার্ট" + run: "চালান" + add_chart: "+ চার্ট" + remove_all_tooltip: "সব ফলাফল সরান" + select_valid_channels: "প্রথমে বৈধ চ্যানেল নির্বাচন করুন" + run_add_tooltip: "বিশ্লেষণ চালান এবং ফলাফল চার্টে যোগ করুন" + run_tooltip: "বিশ্লেষণ চালান (পরে চার্টে যোগ করুন)" + remove_result_tooltip: "ফলাফল সরান" + add_to_chart_result: "চ্যানেল হিসেবে চার্টে যোগ করুন" + +# Tool switcher (src/ui/tool_switcher.rs) +tools: + log_viewer: "লগ ভিউয়ার" + scatter_plots: "স্ক্যাটার প্লট" + histogram: "হিস্টোগ্রাম" + +# Activity bar (src/ui/activity_bar.rs) +activity: + files_tooltip: "ফাইলসমূহ" + channels_tooltip: "চ্যানেলসমূহ" + tools_tooltip: "টুলস" + settings_tooltip: "সেটিংস" diff --git a/i18n/de.yaml b/i18n/de.yaml new file mode 100644 index 0000000..7efa36b --- /dev/null +++ b/i18n/de.yaml @@ -0,0 +1,335 @@ +# German translations for UltraLog + +# Menu bar (src/ui/menu.rs) +menu: + file: "Datei" + open_log_file: "Logdatei öffnen..." + close_tab: "Tab schließen" + export: "Exportieren" + export_png: "Als PNG exportieren..." + export_pdf: "Als PDF exportieren..." + export_histogram_pdf: "Histogramm als PDF exportieren..." + view: "Ansicht" + tool_mode: "Werkzeugmodus" + log_viewer: "Log-Betrachter" + scatter_plots: "Streudiagramme" + histogram: "Histogramm" + side_panel: "Seitenleiste" + files: "Dateien" + channels: "Kanäle" + tools: "Werkzeuge" + settings: "Einstellungen" + help: "Hilfe" + documentation: "Dokumentation" + report_issue: "Problem melden" + support_development: "Entwicklung unterstützen" + check_for_updates: "Nach Updates suchen" + checking_for_updates: "Suche nach Updates..." + version: "Version %{version}" + +# Files panel (src/ui/files_panel.rs) +files: + loading: "Lade %{filename}..." + loaded_files: "Geladene Dateien (%{count})" + add_file: "Datei hinzufügen" + select_file: "Datei auswählen" + or: "oder" + drop_file_here: "Datei hier ablegen" + file_info: "%{ecu} • %{channels} Kan. • %{points} Pkt." + +# Channels panel (src/ui/channels_panel.rs) +channels: + computed_channels: "Berechnete Kanäle" + computed_channels_tooltip: "Virtuelle Kanäle aus mathematischen Formeln erstellen" + search_hint: "Kanäle suchen..." + selected_count: "Ausgewählt: %{selected} / %{max} | Gesamt: %{total}" + no_file_selected: "Keine Datei ausgewählt" + load_file_to_view: "Laden Sie eine Datei, um Kanäle anzuzeigen" + with_data: "Mit Daten (%{count})" + empty: "Leer (%{count})" + +# Settings panel (src/ui/settings_panel.rs) +settings: + language: "Sprache" + language_desc: "Anzeigesprache auswählen" + display: "Anzeige" + color_blind_mode: "Farbenblind-Modus" + color_blind_desc: "Barrierefreie Farbpalette verwenden (Wong-Palette)" + font_size: "Schriftgröße:" + cursor_tracking: "Cursor-Verfolgung" + cursor_tracking_desc: "Cursor beim Scrubben zentriert halten" + window: "Fenster:" + field_names: "Feldnamen" + field_normalization: "Feldnormalisierung" + field_normalization_desc: "Kanalnamen über ECU-Typen hinweg standardisieren" + custom_mappings: "%{count} benutzerdefinierte Zuordnungen" + edit_custom_mappings: "Benutzerdefinierte Zuordnungen bearbeiten" + units: "Einheiten" + units_desc: "Bevorzugte Anzeigeeinheiten auswählen" + temperature: "Temperatur:" + pressure: "Druck:" + speed: "Geschwindigkeit:" + distance: "Entfernung:" + fuel_economy: "Kraftstoffverbrauch:" + volume: "Volumen:" + flow_rate: "Durchflussrate:" + acceleration: "Beschleunigung:" + updates: "Updates" + check_on_startup: "Beim Start prüfen" + auto_check_desc: "Automatisch nach neuen Versionen suchen" + check_for_updates: "Nach Updates suchen" + checking: "Prüfe..." + current_version: "Aktuelle Version: %{version}" + update_available: "Update verfügbar: v%{version}" + view_details: "Details anzeigen" + +# Timeline (src/ui/timeline.rs) +timeline: + speed: "Geschwindigkeit:" + time: "Zeit: %{time}" + record: "Datensatz %{current} von %{total}" + +# Formula editor (src/ui/formula_editor.rs) +formula: + new_computed_channel: "Neuer berechneter Kanal" + edit_computed_channel: "Berechneten Kanal bearbeiten" + name: "Name:" + name_hint: "z.B. RPM Delta" + formula: "Formel:" + formula_hint: "z.B. RPM - RPM[-1]" + insert: "Einfügen:" + insert_tooltip: "%{op} einfügen" + prev_sample_tooltip: "Vorheriger Messwert (Index-Offset)" + time_ago_tooltip: "Wert vor 0,1 Sekunden" + error: "Fehler:" + formula_valid: "Formel gültig" + unit: "Einheit:" + unit_hint: "z.B. RPM/s" + description: "Beschreibung:" + description_hint: "Optionale Beschreibung" + available_channels: "Verfügbare Kanäle" + no_channels_available: "Keine Kanäle verfügbar - laden Sie zuerst eine Logdatei" + click_to_insert: "Klicken zum Einfügen in die Formel:" + preview: "Vorschau" + min: "Min:" + avg: "Mittel:" + max: "Max:" + sample: "Messwert:" + cancel: "Abbrechen" + save: "Speichern" + validate: "Validieren" + +# Update dialog (src/ui/update_dialog.rs) +update: + update_available: "Update verfügbar" + new_version_available: "Eine neue Version ist verfügbar!" + current_version: "Aktuelle Version:" + new_version: "Neue Version:" + download_size: "Downloadgröße: %{size} MB" + release_notes: "Versionshinweise" + download_install: "Herunterladen & Installieren" + view_on_github: "Auf GitHub ansehen" + later: "Später" + downloading: "Update wird heruntergeladen..." + please_wait: "Bitte warten..." + download_complete: "Download abgeschlossen!" + install_instructions: "Klicken Sie auf Installieren, um das Update anzuwenden." + windows_restart: "Die Anwendung wird geschlossen und automatisch neu gestartet." + macos_dmg: "Das DMG wird geöffnet - ziehen Sie UltraLog in Programme." + linux_restart: "Die Anwendung wird geschlossen und automatisch neu gestartet." + install_now: "Jetzt installieren" + install_later: "Später installieren" + update_error: "Update-Fehler" + close: "Schließen" + update_saved: "Update wurde im temporären Ordner gespeichert." + +# Toast messages (src/app.rs) +toast: + file_already_loaded: "Datei bereits geladen" + file_loaded: "Datei erfolgreich geladen" + max_channels: "Maximal 10 Kanäle erreicht" + channel_already_selected: "Kanal bereits ausgewählt" + channel_from_active_tab: "Kanal muss vom aktiven Tab stammen" + no_active_tab: "Kein aktiver Tab" + library_saved: "Bibliothek gespeichert" + library_save_failed: "Bibliothek konnte nicht gespeichert werden: %{error}" + channel_saved: "Kanal in Bibliothek gespeichert" + up_to_date: "Sie verwenden die neueste Version" + update_check_failed: "Update-Prüfung fehlgeschlagen: %{error}" + update_downloaded: "Update erfolgreich heruntergeladen" + download_failed: "Download fehlgeschlagen: %{error}" + export_png_success: "Diagramm als PNG exportiert" + export_pdf_success: "Diagramm als PDF exportiert" + export_failed: "Export fehlgeschlagen: %{error}" + histogram_exported_png: "Histogramm als PNG exportiert" + histogram_exported_pdf: "Histogramm als PDF exportiert" + scatter_exported_png: "Streudiagramm als PNG exportiert" + scatter_exported_pdf: "Streudiagramm als PDF exportiert" + failed_to_save: "Speichern fehlgeschlagen: %{error}" + failed_to_apply: "Anwenden fehlgeschlagen: %{error}" + evaluation_failed: "Auswertung fehlgeschlagen: %{error}" + template_duplicated: "Vorlage dupliziert" + applied_template: "'%{name}' angewendet" + added_to_chart: "'%{name}' zum Diagramm hinzugefügt" + channel_already_on_chart: "Kanal bereits im Diagramm" + max_channels_reached: "Maximal 10 Kanäle erreicht" + analysis_complete: "Analyse abgeschlossen: %{name}" + analysis_failed: "Analyse fehlgeschlagen: %{error}" + analyzer_not_found: "Analysator nicht gefunden: %{id}" + no_file_selected: "Keine Datei ausgewählt" + file_not_found: "Datei nicht gefunden" + +# Common/shared strings +common: + loading: "Laden..." + error: "Fehler" + success: "Erfolg" + warning: "Warnung" + info: "Info" + ok: "OK" + yes: "Ja" + no: "Nein" + remove: "Entfernen" + add: "Hinzufügen" + edit: "Bearbeiten" + delete: "Löschen" + duplicate: "Duplizieren" + apply: "Anwenden" + close: "Schließen" + reset: "Zurücksetzen" + +# Computed channels manager (src/ui/computed_channels_manager.rs) +computed: + title: "Berechnete Kanäle" + show_help: "Formel-Syntaxhilfe anzeigen" + new: "+ Neu" + description: "Virtuelle Kanäle aus mathematischen Formeln erstellen." + quick_create: "Schnellerstellung:" + rate_of_change: "Änderungsrate" + rate_of_change_hint: "Erstellen: Channel - Channel[-1]" + rate_of_change_desc: "Änderungsrate pro Messwert" + moving_avg: "Gleitender Durchschnitt" + moving_avg_hint: "Erstellen: 3-Messwert gleitender Durchschnitt" + moving_avg_desc: "3-Messwert gleitender Durchschnitt zur Glättung" + deviation: "% Abweichung" + deviation_hint: "Erstellen: Prozentuale Abweichung von einem Zielwert" + deviation_desc: "Prozentuale Abweichung vom Zielwert" + your_library: "Ihre Bibliothek (%{count})" + search: "🔍 Suchen..." + no_channels: "Noch keine berechneten Kanäle" + get_started: "Verwenden Sie Schnellerstellung oder klicken Sie auf '+ Neu'" + copy: "Kopie" + builtin_template: "Integrierte Vorlage" + applied_to_file: "Auf aktuelle Datei angewendet (%{count})" + apply_templates_hint: "Vorlagen aus der obigen Bibliothek anwenden" + add_chart: "+ Diagramm" + add_to_chart: "Zum Diagramm hinzufügen" + load_file_hint: "Laden Sie eine Logdatei, um berechnete Kanäle anzuwenden" + help_title: "Formel-Hilfe" + help_channel_refs: "Kanalreferenzen" + help_current_value: "Aktueller Wert des RPM-Kanals" + help_quoted_channel: "Kanäle mit Leerzeichen (Anführungszeichen verwenden)" + help_prev_sample: "Vorheriger Messwert (Index-Offset)" + help_samples_ahead: "2 Messwerte voraus" + help_time_offset: "Wert vor 100ms (Zeit-Offset)" + help_operators: "Operatoren" + help_basic_math: "Grundrechenarten" + help_power: "Potenz (z.B. RPM^2)" + help_grouping: "Gruppierung" + help_functions: "Funktionen" + help_trig: "Trigonometrie" + help_sqrt_abs: "Quadratwurzel, Absolutwert" + help_log: "Logarithmen, Exponential" + help_minmax: "Minimum, Maximum" + help_rounding: "Rundung" + help_statistics: "Statistik (für Anomalieerkennung)" + help_mean: "Mittelwert des gesamten RPM-Kanals" + help_stdev: "Standardabweichung" + help_min_max: "Min/Max-Werte" + help_examples: "Beispiele" + help_ex_rate: "RPM-Änderung pro Messwert" + help_ex_deviation: "AFR-Abweichung in %" + help_ex_zscore: "Z-Score" + +# Normalization editor (src/ui/normalization_editor.rs) +normalization: + title: "Feldnormalisierungs-Editor" + field_mappings: "Feldnamen-Zuordnungen" + reset_defaults: "Auf Standardwerte zurücksetzen" + extend_builtin: "Integrierte Zuordnungen erweitern" + extend_description: "Fügen Sie eigene Quellnamen hinzu, die auf vorhandene normalisierte Feldnamen abgebildet werden." + source_name: "Quellname:" + source_hint: "z.B. MeinCustomRPM" + maps_to: "Zuordnung zu:" + select: "Auswählen..." + create_new: "Neue Zuordnung erstellen" + create_description: "Definieren Sie völlig neue normalisierte Namen für Ihre benutzerdefinierten Kanäle." + custom_source_hint: "z.B. Sensor_XYZ" + display_as: "Anzeigen als:" + custom_target_hint: "z.B. Benutzerdefinierter Sensor" + your_mappings: "Ihre benutzerdefinierten Zuordnungen" + source: "Quelle" + builtin_reference: "Referenz der integrierten Zuordnungen" + builtin_description: "Dies sind die Standardzuordnungen. Sie können sie erweitern, indem Sie oben neue Quellnamen hinzufügen." + +# Scatter plot (src/ui/scatter_plot.rs) +scatter: + no_file_loaded: "Laden Sie eine Logdatei, um Streudiagramme zu verwenden" + x_axis: "X-Achse:" + y_axis: "Y-Achse:" + z_axis: "Z-Achse:" + hits: "Treffer" + hits_label: "Treffer:" + remove: "X" + +# Histogram (src/ui/histogram.rs) +histogram: + no_file_loaded: "Laden Sie eine Logdatei, um das Histogramm zu verwenden" + x_axis: "X-Achse:" + y_axis: "Y-Achse:" + z_axis: "Z-Achse:" + mode: "Modus:" + average_z: "Durchschnitt Z" + hit_count: "Trefferanzahl" + grid: "Raster:" + +# Chart (src/ui/chart.rs) +chart: + select_channels: "Kanäle auswählen, um Diagramm anzuzeigen" + +# Analysis panel (src/ui/analysis_panel.rs) +analysis: + window_title: "Analysewerkzeuge" + no_file_loaded: "Keine Logdatei geladen" + load_file_help: "Laden Sie eine Logdatei, um auf die Analysewerkzeuge zuzugreifen." + category_all: "Alle" + category_filters: "Filter" + category_statistics: "Statistik" + category_afr: "AFR" + category_derived: "Abgeleitet" + results_count: "Ergebnisse (%{count})" + clear_all: "Alle löschen" + all_tools: "Alle Werkzeuge" + no_analyzers: "Keine Analysatoren in dieser Kategorie" + run_and_chart: "Ausführen & Diagramm" + run: "Ausführen" + add_chart: "+ Diagramm" + remove_all_tooltip: "Alle Ergebnisse entfernen" + select_valid_channels: "Wählen Sie zuerst gültige Kanäle aus" + run_add_tooltip: "Analyse ausführen und Ergebnis zum Diagramm hinzufügen" + run_tooltip: "Analyse ausführen (später zum Diagramm hinzufügen)" + remove_result_tooltip: "Ergebnis entfernen" + add_to_chart_result: "Als Kanal zum Diagramm hinzufügen" + +# Tool switcher (src/ui/tool_switcher.rs) +tools: + log_viewer: "Log-Betrachter" + scatter_plots: "Streudiagramme" + histogram: "Histogramm" + +# Activity bar (src/ui/activity_bar.rs) +activity: + files_tooltip: "Dateien" + channels_tooltip: "Kanäle" + tools_tooltip: "Werkzeuge" + settings_tooltip: "Einstellungen" diff --git a/i18n/en.yaml b/i18n/en.yaml new file mode 100644 index 0000000..a9e4a77 --- /dev/null +++ b/i18n/en.yaml @@ -0,0 +1,335 @@ +# English translations for UltraLog + +# Menu bar (src/ui/menu.rs) +menu: + file: "File" + open_log_file: "Open Log File..." + close_tab: "Close Tab" + export: "Export" + export_png: "Export as PNG..." + export_pdf: "Export as PDF..." + export_histogram_pdf: "Export Histogram as PDF..." + view: "View" + tool_mode: "Tool Mode" + log_viewer: "Log Viewer" + scatter_plots: "Scatter Plots" + histogram: "Histogram" + side_panel: "Side Panel" + files: "Files" + channels: "Channels" + tools: "Tools" + settings: "Settings" + help: "Help" + documentation: "Documentation" + report_issue: "Report Issue" + support_development: "Support Development" + check_for_updates: "Check for Updates" + checking_for_updates: "Checking for Updates..." + version: "Version %{version}" + +# Files panel (src/ui/files_panel.rs) +files: + loading: "Loading %{filename}..." + loaded_files: "Loaded Files (%{count})" + add_file: "Add File" + select_file: "Select a file" + or: "or" + drop_file_here: "Drop file here" + file_info: "%{ecu} • %{channels} ch • %{points} pts" + +# Channels panel (src/ui/channels_panel.rs) +channels: + computed_channels: "Computed Channels" + computed_channels_tooltip: "Create virtual channels from mathematical formulas" + search_hint: "Search channels..." + selected_count: "Selected: %{selected} / %{max} | Total: %{total}" + no_file_selected: "No file selected" + load_file_to_view: "Load a file to view channels" + with_data: "With Data (%{count})" + empty: "Empty (%{count})" + +# Settings panel (src/ui/settings_panel.rs) +settings: + language: "Language" + language_desc: "Select display language" + display: "Display" + color_blind_mode: "Color Blind Mode" + color_blind_desc: "Use accessible color palette (Wong's palette)" + font_size: "Font Size:" + cursor_tracking: "Cursor Tracking" + cursor_tracking_desc: "Keep cursor centered while scrubbing" + window: "Window:" + field_names: "Field Names" + field_normalization: "Field Normalization" + field_normalization_desc: "Standardize channel names across ECU types" + custom_mappings: "%{count} custom mappings" + edit_custom_mappings: "Edit Custom Mappings" + units: "Units" + units_desc: "Select preferred units for display" + temperature: "Temperature:" + pressure: "Pressure:" + speed: "Speed:" + distance: "Distance:" + fuel_economy: "Fuel Economy:" + volume: "Volume:" + flow_rate: "Flow Rate:" + acceleration: "Acceleration:" + updates: "Updates" + check_on_startup: "Check on startup" + auto_check_desc: "Automatically check for new versions" + check_for_updates: "Check for Updates" + checking: "Checking..." + current_version: "Current version: %{version}" + update_available: "Update available: v%{version}" + view_details: "View Details" + +# Timeline (src/ui/timeline.rs) +timeline: + speed: "Speed:" + time: "Time: %{time}" + record: "Record %{current} of %{total}" + +# Formula editor (src/ui/formula_editor.rs) +formula: + new_computed_channel: "New Computed Channel" + edit_computed_channel: "Edit Computed Channel" + name: "Name:" + name_hint: "e.g., RPM Delta" + formula: "Formula:" + formula_hint: "e.g., RPM - RPM[-1]" + insert: "Insert:" + insert_tooltip: "Insert %{op}" + prev_sample_tooltip: "Previous sample (index offset)" + time_ago_tooltip: "Value 0.1 seconds ago" + error: "Error:" + formula_valid: "Formula valid" + unit: "Unit:" + unit_hint: "e.g., RPM/s" + description: "Description:" + description_hint: "Optional description" + available_channels: "Available Channels" + no_channels_available: "No channels available - load a log file first" + click_to_insert: "Click to insert into formula:" + preview: "Preview" + min: "Min:" + avg: "Avg:" + max: "Max:" + sample: "Sample:" + cancel: "Cancel" + save: "Save" + validate: "Validate" + +# Update dialog (src/ui/update_dialog.rs) +update: + update_available: "Update Available" + new_version_available: "A new version is available!" + current_version: "Current version:" + new_version: "New version:" + download_size: "Download size: %{size} MB" + release_notes: "Release Notes" + download_install: "Download & Install" + view_on_github: "View on GitHub" + later: "Later" + downloading: "Downloading update..." + please_wait: "Please wait..." + download_complete: "Download complete!" + install_instructions: "Click Install to apply the update." + windows_restart: "The application will close and restart automatically." + macos_dmg: "The DMG will open - drag UltraLog to Applications." + linux_restart: "The application will close and restart automatically." + install_now: "Install Now" + install_later: "Install Later" + update_error: "Update Error" + close: "Close" + update_saved: "Update saved to your temp folder." + +# Toast messages (src/app.rs) +toast: + file_already_loaded: "File already loaded" + file_loaded: "File loaded successfully" + max_channels: "Maximum 10 channels reached" + channel_already_selected: "Channel already selected" + channel_from_active_tab: "Channel must be from the active tab's file" + no_active_tab: "No active tab" + library_saved: "Library saved" + library_save_failed: "Failed to save library: %{error}" + channel_saved: "Channel saved to library" + up_to_date: "You're running the latest version" + update_check_failed: "Update check failed: %{error}" + update_downloaded: "Update downloaded successfully" + download_failed: "Download failed: %{error}" + export_png_success: "Chart exported as PNG" + export_pdf_success: "Chart exported as PDF" + export_failed: "Export failed: %{error}" + histogram_exported_png: "Histogram exported as PNG" + histogram_exported_pdf: "Histogram exported as PDF" + scatter_exported_png: "Scatter plot exported as PNG" + scatter_exported_pdf: "Scatter plot exported as PDF" + failed_to_save: "Failed to save: %{error}" + failed_to_apply: "Failed to apply: %{error}" + evaluation_failed: "Evaluation failed: %{error}" + template_duplicated: "Template duplicated" + applied_template: "Applied '%{name}'" + added_to_chart: "'%{name}' added to chart" + channel_already_on_chart: "Channel already on chart" + max_channels_reached: "Maximum 10 channels reached" + analysis_complete: "Analysis complete: %{name}" + analysis_failed: "Analysis failed: %{error}" + analyzer_not_found: "Analyzer not found: %{id}" + no_file_selected: "No file selected" + file_not_found: "File not found" + +# Common/shared strings +common: + loading: "Loading..." + error: "Error" + success: "Success" + warning: "Warning" + info: "Info" + ok: "OK" + yes: "Yes" + no: "No" + remove: "Remove" + add: "Add" + edit: "Edit" + delete: "Delete" + duplicate: "Duplicate" + apply: "Apply" + close: "Close" + reset: "Reset" + +# Computed channels manager (src/ui/computed_channels_manager.rs) +computed: + title: "Computed Channels" + show_help: "Show formula syntax help" + new: "+ New" + description: "Create virtual channels from mathematical formulas." + quick_create: "Quick Create:" + rate_of_change: "Rate of Change" + rate_of_change_hint: "Create: Channel - Channel[-1]" + rate_of_change_desc: "Rate of change per sample" + moving_avg: "Moving Avg" + moving_avg_hint: "Create: 3-sample moving average" + moving_avg_desc: "3-sample moving average for smoothing" + deviation: "% Deviation" + deviation_hint: "Create: Percentage deviation from a target" + deviation_desc: "Percentage deviation from target value" + your_library: "Your Library (%{count})" + search: "🔍 Search..." + no_channels: "No computed channels yet" + get_started: "Use Quick Create or click '+ New' to get started" + copy: "copy" + builtin_template: "Built-in template" + applied_to_file: "Applied to Current File (%{count})" + apply_templates_hint: "Apply templates from the library above" + add_chart: "+ Chart" + add_to_chart: "Add to chart" + load_file_hint: "Load a log file to apply computed channels" + help_title: "Formula Help" + help_channel_refs: "Channel References" + help_current_value: "Current value of RPM channel" + help_quoted_channel: "Channels with spaces (use quotes)" + help_prev_sample: "Previous sample (index offset)" + help_samples_ahead: "2 samples ahead" + help_time_offset: "Value 100ms ago (time offset)" + help_operators: "Operators" + help_basic_math: "Basic math" + help_power: "Power (e.g., RPM^2)" + help_grouping: "Grouping" + help_functions: "Functions" + help_trig: "Trigonometry" + help_sqrt_abs: "Square root, absolute value" + help_log: "Logarithms, exponential" + help_minmax: "Minimum, maximum" + help_rounding: "Rounding" + help_statistics: "Statistics (for anomaly detection)" + help_mean: "Mean of entire RPM channel" + help_stdev: "Standard deviation" + help_min_max: "Min/max values" + help_examples: "Examples" + help_ex_rate: "RPM change per sample" + help_ex_deviation: "AFR % deviation" + help_ex_zscore: "Z-score" + +# Normalization editor (src/ui/normalization_editor.rs) +normalization: + title: "Field Normalization Editor" + field_mappings: "Field Name Mappings" + reset_defaults: "Reset to Defaults" + extend_builtin: "Extend Built-in Mappings" + extend_description: "Add your own source names that map to existing normalized field names." + source_name: "Source Name:" + source_hint: "e.g., MyCustomRPM" + maps_to: "Maps To:" + select: "Select..." + create_new: "Create New Mapping" + create_description: "Define completely new normalized names for your custom channels." + custom_source_hint: "e.g., Sensor_XYZ" + display_as: "Display As:" + custom_target_hint: "e.g., Custom Sensor" + your_mappings: "Your Custom Mappings" + source: "Source" + builtin_reference: "Built-in Mappings Reference" + builtin_description: "These are the default mappings. You can extend them by adding new source names above." + +# Scatter plot (src/ui/scatter_plot.rs) +scatter: + no_file_loaded: "Load a log file to use scatter plots" + x_axis: "X Axis:" + y_axis: "Y Axis:" + z_axis: "Z Axis:" + hits: "Hits" + hits_label: "Hits:" + remove: "X" + +# Histogram (src/ui/histogram.rs) +histogram: + no_file_loaded: "Load a log file to use histogram" + x_axis: "X Axis:" + y_axis: "Y Axis:" + z_axis: "Z Axis:" + mode: "Mode:" + average_z: "Average Z" + hit_count: "Hit Count" + grid: "Grid:" + +# Chart (src/ui/chart.rs) +chart: + select_channels: "Select channels to display chart" + +# Analysis panel (src/ui/analysis_panel.rs) +analysis: + window_title: "Analysis Tools" + no_file_loaded: "No log file loaded" + load_file_help: "Load a log file to access analysis tools." + category_all: "All" + category_filters: "Filters" + category_statistics: "Statistics" + category_afr: "AFR" + category_derived: "Derived" + results_count: "Results (%{count})" + clear_all: "Clear All" + all_tools: "All Tools" + no_analyzers: "No analyzers in this category" + run_and_chart: "Run & Chart" + run: "Run" + add_chart: "+ Chart" + remove_all_tooltip: "Remove all results" + select_valid_channels: "Select valid channels first" + run_add_tooltip: "Run analysis and add result to chart" + run_tooltip: "Run analysis (add to chart later)" + remove_result_tooltip: "Remove result" + add_to_chart_result: "Add to chart as a channel" + +# Tool switcher (src/ui/tool_switcher.rs) +tools: + log_viewer: "Log Viewer" + scatter_plots: "Scatter Plots" + histogram: "Histogram" + +# Activity bar (src/ui/activity_bar.rs) +activity: + files_tooltip: "Files" + channels_tooltip: "Channels" + tools_tooltip: "Tools" + settings_tooltip: "Settings" diff --git a/i18n/es.yaml b/i18n/es.yaml new file mode 100644 index 0000000..5bd3f1a --- /dev/null +++ b/i18n/es.yaml @@ -0,0 +1,335 @@ +# Traducciones en espanol para UltraLog + +# Barra de menu (src/ui/menu.rs) +menu: + file: "Archivo" + open_log_file: "Abrir Archivo de Log..." + close_tab: "Cerrar Pestana" + export: "Exportar" + export_png: "Exportar como PNG..." + export_pdf: "Exportar como PDF..." + export_histogram_pdf: "Exportar Histograma como PDF..." + view: "Vista" + tool_mode: "Modo de Herramienta" + log_viewer: "Visor de Log" + scatter_plots: "Graficos de Dispersion" + histogram: "Histograma" + side_panel: "Panel Lateral" + files: "Archivos" + channels: "Canales" + tools: "Herramientas" + settings: "Configuracion" + help: "Ayuda" + documentation: "Documentacion" + report_issue: "Reportar Problema" + support_development: "Apoyar el Desarrollo" + check_for_updates: "Buscar Actualizaciones" + checking_for_updates: "Buscando Actualizaciones..." + version: "Version %{version}" + +# Panel de archivos (src/ui/files_panel.rs) +files: + loading: "Cargando %{filename}..." + loaded_files: "Archivos Cargados (%{count})" + add_file: "Agregar Archivo" + select_file: "Seleccionar archivo" + or: "o" + drop_file_here: "Soltar archivo aqui" + file_info: "%{ecu} • %{channels} can • %{points} pts" + +# Panel de canales (src/ui/channels_panel.rs) +channels: + computed_channels: "Canales Calculados" + computed_channels_tooltip: "Crear canales virtuales a partir de formulas matematicas" + search_hint: "Buscar canales..." + selected_count: "Seleccionados: %{selected} / %{max} | Total: %{total}" + no_file_selected: "Ningun archivo seleccionado" + load_file_to_view: "Cargue un archivo para ver canales" + with_data: "Con Datos (%{count})" + empty: "Vacios (%{count})" + +# Panel de configuracion (src/ui/settings_panel.rs) +settings: + language: "Idioma" + language_desc: "Seleccionar idioma de visualizacion" + display: "Pantalla" + color_blind_mode: "Modo Daltonico" + color_blind_desc: "Usar paleta de colores accesible (paleta de Wong)" + font_size: "Tamano de Fuente:" + cursor_tracking: "Seguimiento del Cursor" + cursor_tracking_desc: "Mantener el cursor centrado al desplazar" + window: "Ventana:" + field_names: "Nombres de Campos" + field_normalization: "Normalizacion de Campos" + field_normalization_desc: "Estandarizar nombres de canales entre tipos de ECU" + custom_mappings: "%{count} mapeos personalizados" + edit_custom_mappings: "Editar Mapeos Personalizados" + units: "Unidades" + units_desc: "Seleccionar unidades preferidas para mostrar" + temperature: "Temperatura:" + pressure: "Presion:" + speed: "Velocidad:" + distance: "Distancia:" + fuel_economy: "Economia de Combustible:" + volume: "Volumen:" + flow_rate: "Caudal:" + acceleration: "Aceleracion:" + updates: "Actualizaciones" + check_on_startup: "Verificar al iniciar" + auto_check_desc: "Buscar nuevas versiones automaticamente" + check_for_updates: "Buscar Actualizaciones" + checking: "Verificando..." + current_version: "Version actual: %{version}" + update_available: "Actualizacion disponible: v%{version}" + view_details: "Ver Detalles" + +# Linea de tiempo (src/ui/timeline.rs) +timeline: + speed: "Velocidad:" + time: "Tiempo: %{time}" + record: "Registro %{current} de %{total}" + +# Editor de formulas (src/ui/formula_editor.rs) +formula: + new_computed_channel: "Nuevo Canal Calculado" + edit_computed_channel: "Editar Canal Calculado" + name: "Nombre:" + name_hint: "ej., Delta RPM" + formula: "Formula:" + formula_hint: "ej., RPM - RPM[-1]" + insert: "Insertar:" + insert_tooltip: "Insertar %{op}" + prev_sample_tooltip: "Muestra anterior (desplazamiento de indice)" + time_ago_tooltip: "Valor hace 0.1 segundos" + error: "Error:" + formula_valid: "Formula valida" + unit: "Unidad:" + unit_hint: "ej., RPM/s" + description: "Descripcion:" + description_hint: "Descripcion opcional" + available_channels: "Canales Disponibles" + no_channels_available: "No hay canales disponibles - cargue un archivo de log primero" + click_to_insert: "Clic para insertar en la formula:" + preview: "Vista Previa" + min: "Min:" + avg: "Prom:" + max: "Max:" + sample: "Muestra:" + cancel: "Cancelar" + save: "Guardar" + validate: "Validar" + +# Dialogo de actualizacion (src/ui/update_dialog.rs) +update: + update_available: "Actualizacion Disponible" + new_version_available: "Una nueva version esta disponible!" + current_version: "Version actual:" + new_version: "Nueva version:" + download_size: "Tamano de descarga: %{size} MB" + release_notes: "Notas de la Version" + download_install: "Descargar e Instalar" + view_on_github: "Ver en GitHub" + later: "Mas tarde" + downloading: "Descargando actualizacion..." + please_wait: "Por favor espere..." + download_complete: "Descarga completada!" + install_instructions: "Haga clic en Instalar para aplicar la actualizacion." + windows_restart: "La aplicacion se cerrara y reiniciara automaticamente." + macos_dmg: "El DMG se abrira - arrastre UltraLog a Aplicaciones." + linux_restart: "La aplicacion se cerrara y reiniciara automaticamente." + install_now: "Instalar Ahora" + install_later: "Instalar Despues" + update_error: "Error de Actualizacion" + close: "Cerrar" + update_saved: "Actualizacion guardada en su carpeta temporal." + +# Mensajes toast (src/app.rs) +toast: + file_already_loaded: "Archivo ya cargado" + file_loaded: "Archivo cargado exitosamente" + max_channels: "Maximo 10 canales alcanzado" + channel_already_selected: "Canal ya seleccionado" + channel_from_active_tab: "El canal debe ser del archivo de la pestana activa" + no_active_tab: "Sin pestana activa" + library_saved: "Biblioteca guardada" + library_save_failed: "Error al guardar biblioteca: %{error}" + channel_saved: "Canal guardado en biblioteca" + up_to_date: "Tiene la ultima version" + update_check_failed: "Error al buscar actualizaciones: %{error}" + update_downloaded: "Actualizacion descargada exitosamente" + download_failed: "Error de descarga: %{error}" + export_png_success: "Grafico exportado como PNG" + export_pdf_success: "Grafico exportado como PDF" + export_failed: "Error de exportacion: %{error}" + histogram_exported_png: "Histograma exportado como PNG" + histogram_exported_pdf: "Histograma exportado como PDF" + scatter_exported_png: "Grafico de dispersion exportado como PNG" + scatter_exported_pdf: "Grafico de dispersion exportado como PDF" + failed_to_save: "Error al guardar: %{error}" + failed_to_apply: "Error al aplicar: %{error}" + evaluation_failed: "Error en evaluacion: %{error}" + template_duplicated: "Plantilla duplicada" + applied_template: "Aplicado '%{name}'" + added_to_chart: "'%{name}' agregado al grafico" + channel_already_on_chart: "Canal ya esta en el grafico" + max_channels_reached: "Maximo de 10 canales alcanzado" + analysis_complete: "Analisis completo: %{name}" + analysis_failed: "Error en analisis: %{error}" + analyzer_not_found: "Analizador no encontrado: %{id}" + no_file_selected: "Ningun archivo seleccionado" + file_not_found: "Archivo no encontrado" + +# Cadenas comunes/compartidas +common: + loading: "Cargando..." + error: "Error" + success: "Exito" + warning: "Advertencia" + info: "Info" + ok: "OK" + yes: "Si" + no: "No" + remove: "Eliminar" + add: "Agregar" + edit: "Editar" + delete: "Eliminar" + duplicate: "Duplicar" + apply: "Aplicar" + close: "Cerrar" + reset: "Restablecer" + +# Gestor de canales calculados (src/ui/computed_channels_manager.rs) +computed: + title: "Canales Calculados" + show_help: "Mostrar ayuda de sintaxis de formulas" + new: "+ Nuevo" + description: "Crear canales virtuales a partir de formulas matematicas." + quick_create: "Creacion Rapida:" + rate_of_change: "Tasa de Cambio" + rate_of_change_hint: "Crear: Canal - Canal[-1]" + rate_of_change_desc: "Tasa de cambio por muestra" + moving_avg: "Promedio Movil" + moving_avg_hint: "Crear: promedio movil de 3 muestras" + moving_avg_desc: "Promedio movil de 3 muestras para suavizado" + deviation: "% Desviacion" + deviation_hint: "Crear: Desviacion porcentual de un objetivo" + deviation_desc: "Desviacion porcentual del valor objetivo" + your_library: "Su Biblioteca (%{count})" + search: "🔍 Buscar..." + no_channels: "Sin canales calculados aun" + get_started: "Use Creacion Rapida o clic en '+ Nuevo' para comenzar" + copy: "copia" + builtin_template: "Plantilla incorporada" + applied_to_file: "Aplicados al Archivo Actual (%{count})" + apply_templates_hint: "Aplique plantillas de la biblioteca arriba" + add_chart: "+ Grafico" + add_to_chart: "Agregar al grafico" + load_file_hint: "Cargue un archivo de log para aplicar canales calculados" + help_title: "Ayuda de Formulas" + help_channel_refs: "Referencias de Canales" + help_current_value: "Valor actual del canal RPM" + help_quoted_channel: "Canales con espacios (usar comillas)" + help_prev_sample: "Muestra anterior (desplazamiento de indice)" + help_samples_ahead: "2 muestras adelante" + help_time_offset: "Valor hace 100ms (desplazamiento de tiempo)" + help_operators: "Operadores" + help_basic_math: "Matematicas basicas" + help_power: "Potencia (ej., RPM^2)" + help_grouping: "Agrupacion" + help_functions: "Funciones" + help_trig: "Trigonometria" + help_sqrt_abs: "Raiz cuadrada, valor absoluto" + help_log: "Logaritmos, exponencial" + help_minmax: "Minimo, maximo" + help_rounding: "Redondeo" + help_statistics: "Estadisticas (para deteccion de anomalias)" + help_mean: "Media del canal RPM completo" + help_stdev: "Desviacion estandar" + help_min_max: "Valores min/max" + help_examples: "Ejemplos" + help_ex_rate: "Cambio de RPM por muestra" + help_ex_deviation: "% desviacion AFR" + help_ex_zscore: "Puntuacion Z" + +# Editor de normalizacion (src/ui/normalization_editor.rs) +normalization: + title: "Editor de Normalizacion de Campos" + field_mappings: "Mapeos de Nombres de Campos" + reset_defaults: "Restablecer Valores Predeterminados" + extend_builtin: "Extender Mapeos Incorporados" + extend_description: "Agregar sus propios nombres fuente que mapeen a nombres de campos normalizados existentes." + source_name: "Nombre Fuente:" + source_hint: "ej., MiRPMPersonalizado" + maps_to: "Mapea A:" + select: "Seleccionar..." + create_new: "Crear Nuevo Mapeo" + create_description: "Definir nombres normalizados completamente nuevos para sus canales personalizados." + custom_source_hint: "ej., Sensor_XYZ" + display_as: "Mostrar Como:" + custom_target_hint: "ej., Sensor Personalizado" + your_mappings: "Sus Mapeos Personalizados" + source: "Fuente" + builtin_reference: "Referencia de Mapeos Incorporados" + builtin_description: "Estos son los mapeos predeterminados. Puede extenderlos agregando nuevos nombres fuente arriba." + +# Grafico de dispersion (src/ui/scatter_plot.rs) +scatter: + no_file_loaded: "Cargue un archivo de log para usar graficos de dispersion" + x_axis: "Eje X:" + y_axis: "Eje Y:" + z_axis: "Eje Z:" + hits: "Impactos" + hits_label: "Impactos:" + remove: "X" + +# Histograma (src/ui/histogram.rs) +histogram: + no_file_loaded: "Cargue un archivo de log para usar histograma" + x_axis: "Eje X:" + y_axis: "Eje Y:" + z_axis: "Eje Z:" + mode: "Modo:" + average_z: "Promedio Z" + hit_count: "Conteo de Impactos" + grid: "Cuadricula:" + +# Grafico (src/ui/chart.rs) +chart: + select_channels: "Seleccione canales para mostrar grafico" + +# Panel de analisis (src/ui/analysis_panel.rs) +analysis: + window_title: "Herramientas de Analisis" + no_file_loaded: "Sin archivo de log cargado" + load_file_help: "Cargue un archivo de log para acceder a herramientas de analisis." + category_all: "Todos" + category_filters: "Filtros" + category_statistics: "Estadisticas" + category_afr: "AFR" + category_derived: "Derivados" + results_count: "Resultados (%{count})" + clear_all: "Limpiar Todo" + all_tools: "Todas las Herramientas" + no_analyzers: "Sin analizadores en esta categoria" + run_and_chart: "Ejecutar y Graficar" + run: "Ejecutar" + add_chart: "+ Grafico" + remove_all_tooltip: "Eliminar todos los resultados" + select_valid_channels: "Seleccione canales validos primero" + run_add_tooltip: "Ejecutar analisis y agregar resultado al grafico" + run_tooltip: "Ejecutar analisis (agregar al grafico despues)" + remove_result_tooltip: "Eliminar resultado" + add_to_chart_result: "Agregar al grafico como canal" + +# Selector de herramientas (src/ui/tool_switcher.rs) +tools: + log_viewer: "Visor de Log" + scatter_plots: "Graficos de Dispersion" + histogram: "Histograma" + +# Barra de actividad (src/ui/activity_bar.rs) +activity: + files_tooltip: "Archivos" + channels_tooltip: "Canales" + tools_tooltip: "Herramientas" + settings_tooltip: "Configuracion" diff --git a/i18n/fr.yaml b/i18n/fr.yaml new file mode 100644 index 0000000..476f795 --- /dev/null +++ b/i18n/fr.yaml @@ -0,0 +1,335 @@ +# Traductions francaises pour UltraLog + +# Menu bar (src/ui/menu.rs) +menu: + file: "Fichier" + open_log_file: "Ouvrir un fichier journal..." + close_tab: "Fermer l'onglet" + export: "Exporter" + export_png: "Exporter en PNG..." + export_pdf: "Exporter en PDF..." + export_histogram_pdf: "Exporter l'histogramme en PDF..." + view: "Affichage" + tool_mode: "Mode outil" + log_viewer: "Visionneuse de journaux" + scatter_plots: "Nuages de points" + histogram: "Histogramme" + side_panel: "Panneau lateral" + files: "Fichiers" + channels: "Canaux" + tools: "Outils" + settings: "Parametres" + help: "Aide" + documentation: "Documentation" + report_issue: "Signaler un probleme" + support_development: "Soutenir le developpement" + check_for_updates: "Rechercher des mises a jour" + checking_for_updates: "Recherche de mises a jour..." + version: "Version %{version}" + +# Files panel (src/ui/files_panel.rs) +files: + loading: "Chargement de %{filename}..." + loaded_files: "Fichiers charges (%{count})" + add_file: "Ajouter un fichier" + select_file: "Selectionnez un fichier" + or: "ou" + drop_file_here: "Deposez le fichier ici" + file_info: "%{ecu} - %{channels} canaux - %{points} pts" + +# Channels panel (src/ui/channels_panel.rs) +channels: + computed_channels: "Canaux calcules" + computed_channels_tooltip: "Creer des canaux virtuels a partir de formules mathematiques" + search_hint: "Rechercher des canaux..." + selected_count: "Selectionnes : %{selected} / %{max} | Total : %{total}" + no_file_selected: "Aucun fichier selectionne" + load_file_to_view: "Chargez un fichier pour voir les canaux" + with_data: "Avec donnees (%{count})" + empty: "Vides (%{count})" + +# Settings panel (src/ui/settings_panel.rs) +settings: + language: "Langue" + language_desc: "Selectionnez la langue d'affichage" + display: "Affichage" + color_blind_mode: "Mode daltonien" + color_blind_desc: "Utiliser une palette de couleurs accessible (palette de Wong)" + font_size: "Taille de police :" + cursor_tracking: "Suivi du curseur" + cursor_tracking_desc: "Garder le curseur centre pendant le defilement" + window: "Fenetre :" + field_names: "Noms des champs" + field_normalization: "Normalisation des champs" + field_normalization_desc: "Standardiser les noms de canaux entre les types d'ECU" + custom_mappings: "%{count} correspondances personnalisees" + edit_custom_mappings: "Modifier les correspondances personnalisees" + units: "Unites" + units_desc: "Selectionnez les unites preferees pour l'affichage" + temperature: "Temperature :" + pressure: "Pression :" + speed: "Vitesse :" + distance: "Distance :" + fuel_economy: "Consommation :" + volume: "Volume :" + flow_rate: "Debit :" + acceleration: "Acceleration :" + updates: "Mises a jour" + check_on_startup: "Verifier au demarrage" + auto_check_desc: "Verifier automatiquement les nouvelles versions" + check_for_updates: "Rechercher des mises a jour" + checking: "Verification..." + current_version: "Version actuelle : %{version}" + update_available: "Mise a jour disponible : v%{version}" + view_details: "Voir les details" + +# Timeline (src/ui/timeline.rs) +timeline: + speed: "Vitesse :" + time: "Temps : %{time}" + record: "Enregistrement %{current} sur %{total}" + +# Formula editor (src/ui/formula_editor.rs) +formula: + new_computed_channel: "Nouveau canal calcule" + edit_computed_channel: "Modifier le canal calcule" + name: "Nom :" + name_hint: "ex. : Delta RPM" + formula: "Formule :" + formula_hint: "ex. : RPM - RPM[-1]" + insert: "Inserer :" + insert_tooltip: "Inserer %{op}" + prev_sample_tooltip: "Echantillon precedent (decalage d'index)" + time_ago_tooltip: "Valeur il y a 0,1 seconde" + error: "Erreur :" + formula_valid: "Formule valide" + unit: "Unite :" + unit_hint: "ex. : RPM/s" + description: "Description :" + description_hint: "Description facultative" + available_channels: "Canaux disponibles" + no_channels_available: "Aucun canal disponible - chargez d'abord un fichier journal" + click_to_insert: "Cliquez pour inserer dans la formule :" + preview: "Apercu" + min: "Min :" + avg: "Moy :" + max: "Max :" + sample: "Echantillon :" + cancel: "Annuler" + save: "Enregistrer" + validate: "Valider" + +# Update dialog (src/ui/update_dialog.rs) +update: + update_available: "Mise a jour disponible" + new_version_available: "Une nouvelle version est disponible !" + current_version: "Version actuelle :" + new_version: "Nouvelle version :" + download_size: "Taille du telechargement : %{size} Mo" + release_notes: "Notes de version" + download_install: "Telecharger et installer" + view_on_github: "Voir sur GitHub" + later: "Plus tard" + downloading: "Telechargement de la mise a jour..." + please_wait: "Veuillez patienter..." + download_complete: "Telechargement termine !" + install_instructions: "Cliquez sur Installer pour appliquer la mise a jour." + windows_restart: "L'application va se fermer et redemarrer automatiquement." + macos_dmg: "Le DMG va s'ouvrir - glissez UltraLog dans Applications." + linux_restart: "L'application va se fermer et redemarrer automatiquement." + install_now: "Installer maintenant" + install_later: "Installer plus tard" + update_error: "Erreur de mise a jour" + close: "Fermer" + update_saved: "Mise a jour enregistree dans votre dossier temporaire." + +# Toast messages (src/app.rs) +toast: + file_already_loaded: "Fichier deja charge" + file_loaded: "Fichier charge avec succes" + max_channels: "Maximum de 10 canaux atteint" + channel_already_selected: "Canal deja selectionne" + channel_from_active_tab: "Le canal doit provenir du fichier de l'onglet actif" + no_active_tab: "Aucun onglet actif" + library_saved: "Bibliotheque enregistree" + library_save_failed: "Echec de l'enregistrement de la bibliotheque : %{error}" + channel_saved: "Canal enregistre dans la bibliotheque" + up_to_date: "Vous utilisez la derniere version" + update_check_failed: "Echec de la verification des mises a jour : %{error}" + update_downloaded: "Mise a jour telechargee avec succes" + download_failed: "Echec du telechargement : %{error}" + export_png_success: "Graphique exporte en PNG" + export_pdf_success: "Graphique exporte en PDF" + export_failed: "Echec de l'exportation : %{error}" + histogram_exported_png: "Histogramme exporte en PNG" + histogram_exported_pdf: "Histogramme exporte en PDF" + scatter_exported_png: "Nuage de points exporte en PNG" + scatter_exported_pdf: "Nuage de points exporte en PDF" + failed_to_save: "Echec de l'enregistrement : %{error}" + failed_to_apply: "Echec de l'application : %{error}" + evaluation_failed: "Echec de l'evaluation : %{error}" + template_duplicated: "Modele duplique" + applied_template: "Modele '%{name}' applique" + added_to_chart: "'%{name}' ajoute au graphique" + channel_already_on_chart: "Canal deja sur le graphique" + max_channels_reached: "Maximum de 10 canaux atteint" + analysis_complete: "Analyse terminee : %{name}" + analysis_failed: "Echec de l'analyse : %{error}" + analyzer_not_found: "Analyseur introuvable : %{id}" + no_file_selected: "Aucun fichier selectionne" + file_not_found: "Fichier introuvable" + +# Common/shared strings +common: + loading: "Chargement..." + error: "Erreur" + success: "Succes" + warning: "Avertissement" + info: "Info" + ok: "OK" + yes: "Oui" + no: "Non" + remove: "Supprimer" + add: "Ajouter" + edit: "Modifier" + delete: "Supprimer" + duplicate: "Dupliquer" + apply: "Appliquer" + close: "Fermer" + reset: "Reinitialiser" + +# Computed channels manager (src/ui/computed_channels_manager.rs) +computed: + title: "Canaux calcules" + show_help: "Afficher l'aide sur la syntaxe des formules" + new: "+ Nouveau" + description: "Creez des canaux virtuels a partir de formules mathematiques." + quick_create: "Creation rapide :" + rate_of_change: "Taux de variation" + rate_of_change_hint: "Creer : Canal - Canal[-1]" + rate_of_change_desc: "Taux de variation par echantillon" + moving_avg: "Moyenne mobile" + moving_avg_hint: "Creer : moyenne mobile sur 3 echantillons" + moving_avg_desc: "Moyenne mobile sur 3 echantillons pour le lissage" + deviation: "% Ecart" + deviation_hint: "Creer : ecart en pourcentage par rapport a une cible" + deviation_desc: "Ecart en pourcentage par rapport a la valeur cible" + your_library: "Votre bibliotheque (%{count})" + search: "Rechercher..." + no_channels: "Aucun canal calcule pour l'instant" + get_started: "Utilisez Creation rapide ou cliquez sur '+ Nouveau' pour commencer" + copy: "copier" + builtin_template: "Modele integre" + applied_to_file: "Appliques au fichier actuel (%{count})" + apply_templates_hint: "Appliquez des modeles depuis la bibliotheque ci-dessus" + add_chart: "+ Graphique" + add_to_chart: "Ajouter au graphique" + load_file_hint: "Chargez un fichier journal pour appliquer les canaux calcules" + help_title: "Aide sur les formules" + help_channel_refs: "References de canaux" + help_current_value: "Valeur actuelle du canal RPM" + help_quoted_channel: "Canaux avec espaces (utilisez des guillemets)" + help_prev_sample: "Echantillon precedent (decalage d'index)" + help_samples_ahead: "2 echantillons en avance" + help_time_offset: "Valeur il y a 100 ms (decalage temporel)" + help_operators: "Operateurs" + help_basic_math: "Mathematiques de base" + help_power: "Puissance (ex. : RPM^2)" + help_grouping: "Groupement" + help_functions: "Fonctions" + help_trig: "Trigonometrie" + help_sqrt_abs: "Racine carree, valeur absolue" + help_log: "Logarithmes, exponentielle" + help_minmax: "Minimum, maximum" + help_rounding: "Arrondi" + help_statistics: "Statistiques (pour la detection d'anomalies)" + help_mean: "Moyenne de l'ensemble du canal RPM" + help_stdev: "Ecart-type" + help_min_max: "Valeurs min/max" + help_examples: "Exemples" + help_ex_rate: "Variation de RPM par echantillon" + help_ex_deviation: "Ecart en % de AFR" + help_ex_zscore: "Score Z" + +# Normalization editor (src/ui/normalization_editor.rs) +normalization: + title: "Editeur de normalisation des champs" + field_mappings: "Correspondances des noms de champs" + reset_defaults: "Reinitialiser aux valeurs par defaut" + extend_builtin: "Etendre les correspondances integrees" + extend_description: "Ajoutez vos propres noms sources qui correspondent aux noms de champs normalises existants." + source_name: "Nom source :" + source_hint: "ex. : MonRPMPersonnalise" + maps_to: "Correspond a :" + select: "Selectionner..." + create_new: "Creer une nouvelle correspondance" + create_description: "Definissez de nouveaux noms normalises pour vos canaux personnalises." + custom_source_hint: "ex. : Capteur_XYZ" + display_as: "Afficher comme :" + custom_target_hint: "ex. : Capteur personnalise" + your_mappings: "Vos correspondances personnalisees" + source: "Source" + builtin_reference: "Reference des correspondances integrees" + builtin_description: "Ce sont les correspondances par defaut. Vous pouvez les etendre en ajoutant de nouveaux noms sources ci-dessus." + +# Scatter plot (src/ui/scatter_plot.rs) +scatter: + no_file_loaded: "Chargez un fichier journal pour utiliser les nuages de points" + x_axis: "Axe X :" + y_axis: "Axe Y :" + z_axis: "Axe Z :" + hits: "Occurrences" + hits_label: "Occurrences :" + remove: "X" + +# Histogram (src/ui/histogram.rs) +histogram: + no_file_loaded: "Chargez un fichier journal pour utiliser l'histogramme" + x_axis: "Axe X :" + y_axis: "Axe Y :" + z_axis: "Axe Z :" + mode: "Mode :" + average_z: "Moyenne Z" + hit_count: "Nombre d'occurrences" + grid: "Grille :" + +# Chart (src/ui/chart.rs) +chart: + select_channels: "Selectionnez des canaux pour afficher le graphique" + +# Analysis panel (src/ui/analysis_panel.rs) +analysis: + window_title: "Outils d'analyse" + no_file_loaded: "Aucun fichier journal charge" + load_file_help: "Chargez un fichier journal pour acceder aux outils d'analyse." + category_all: "Tout" + category_filters: "Filtres" + category_statistics: "Statistiques" + category_afr: "AFR" + category_derived: "Derives" + results_count: "Resultats (%{count})" + clear_all: "Tout effacer" + all_tools: "Tous les outils" + no_analyzers: "Aucun analyseur dans cette categorie" + run_and_chart: "Executer et graphique" + run: "Executer" + add_chart: "+ Graphique" + remove_all_tooltip: "Supprimer tous les resultats" + select_valid_channels: "Selectionnez d'abord des canaux valides" + run_add_tooltip: "Executer l'analyse et ajouter le resultat au graphique" + run_tooltip: "Executer l'analyse (ajouter au graphique plus tard)" + remove_result_tooltip: "Supprimer le resultat" + add_to_chart_result: "Ajouter au graphique comme canal" + +# Tool switcher (src/ui/tool_switcher.rs) +tools: + log_viewer: "Visionneuse de journaux" + scatter_plots: "Nuages de points" + histogram: "Histogramme" + +# Activity bar (src/ui/activity_bar.rs) +activity: + files_tooltip: "Fichiers" + channels_tooltip: "Canaux" + tools_tooltip: "Outils" + settings_tooltip: "Parametres" diff --git a/i18n/hi.yaml b/i18n/hi.yaml new file mode 100644 index 0000000..54c2248 --- /dev/null +++ b/i18n/hi.yaml @@ -0,0 +1,335 @@ +# Hindi translations for UltraLog + +# Menu bar (src/ui/menu.rs) +menu: + file: "फ़ाइल" + open_log_file: "लॉग फ़ाइल खोलें..." + close_tab: "टैब बंद करें" + export: "निर्यात" + export_png: "PNG के रूप में निर्यात करें..." + export_pdf: "PDF के रूप में निर्यात करें..." + export_histogram_pdf: "हिस्टोग्राम PDF के रूप में निर्यात करें..." + view: "दृश्य" + tool_mode: "टूल मोड" + log_viewer: "लॉग व्यूअर" + scatter_plots: "स्कैटर प्लॉट" + histogram: "हिस्टोग्राम" + side_panel: "साइड पैनल" + files: "फ़ाइलें" + channels: "चैनल" + tools: "टूल्स" + settings: "सेटिंग्स" + help: "सहायता" + documentation: "दस्तावेज़ीकरण" + report_issue: "समस्या रिपोर्ट करें" + support_development: "विकास में सहयोग करें" + check_for_updates: "अपडेट की जाँच करें" + checking_for_updates: "अपडेट की जाँच हो रही है..." + version: "संस्करण %{version}" + +# Files panel (src/ui/files_panel.rs) +files: + loading: "%{filename} लोड हो रहा है..." + loaded_files: "लोड की गई फ़ाइलें (%{count})" + add_file: "फ़ाइल जोड़ें" + select_file: "फ़ाइल चुनें" + or: "या" + drop_file_here: "फ़ाइल यहाँ छोड़ें" + file_info: "%{ecu} • %{channels} चैनल • %{points} अंक" + +# Channels panel (src/ui/channels_panel.rs) +channels: + computed_channels: "गणित चैनल" + computed_channels_tooltip: "गणितीय सूत्रों से वर्चुअल चैनल बनाएं" + search_hint: "चैनल खोजें..." + selected_count: "चयनित: %{selected} / %{max} | कुल: %{total}" + no_file_selected: "कोई फ़ाइल चयनित नहीं" + load_file_to_view: "चैनल देखने के लिए फ़ाइल लोड करें" + with_data: "डेटा सहित (%{count})" + empty: "खाली (%{count})" + +# Settings panel (src/ui/settings_panel.rs) +settings: + language: "भाषा" + language_desc: "प्रदर्शन भाषा चुनें" + display: "प्रदर्शन" + color_blind_mode: "कलर ब्लाइंड मोड" + color_blind_desc: "सुलभ रंग पैलेट का उपयोग करें (Wong's पैलेट)" + font_size: "फ़ॉन्ट आकार:" + cursor_tracking: "कर्सर ट्रैकिंग" + cursor_tracking_desc: "स्क्रबिंग के दौरान कर्सर को केंद्रित रखें" + window: "विंडो:" + field_names: "फ़ील्ड नाम" + field_normalization: "फ़ील्ड नॉर्मलाइज़ेशन" + field_normalization_desc: "ECU प्रकारों में चैनल नामों को मानकीकृत करें" + custom_mappings: "%{count} कस्टम मैपिंग" + edit_custom_mappings: "कस्टम मैपिंग संपादित करें" + units: "इकाइयाँ" + units_desc: "प्रदर्शन के लिए पसंदीदा इकाइयाँ चुनें" + temperature: "तापमान:" + pressure: "दबाव:" + speed: "गति:" + distance: "दूरी:" + fuel_economy: "ईंधन दक्षता:" + volume: "आयतन:" + flow_rate: "प्रवाह दर:" + acceleration: "त्वरण:" + updates: "अपडेट" + check_on_startup: "स्टार्टअप पर जाँच करें" + auto_check_desc: "नए संस्करणों की स्वचालित जाँच करें" + check_for_updates: "अपडेट की जाँच करें" + checking: "जाँच हो रही है..." + current_version: "वर्तमान संस्करण: %{version}" + update_available: "उपलब्ध अपडेट: v%{version}" + view_details: "विवरण देखें" + +# Timeline (src/ui/timeline.rs) +timeline: + speed: "गति:" + time: "समय: %{time}" + record: "रिकॉर्ड %{current} में से %{total}" + +# Formula editor (src/ui/formula_editor.rs) +formula: + new_computed_channel: "नया गणित चैनल" + edit_computed_channel: "गणित चैनल संपादित करें" + name: "नाम:" + name_hint: "उदा., RPM Delta" + formula: "सूत्र:" + formula_hint: "उदा., RPM - RPM[-1]" + insert: "डालें:" + insert_tooltip: "%{op} डालें" + prev_sample_tooltip: "पिछला नमूना (इंडेक्स ऑफ़सेट)" + time_ago_tooltip: "0.1 सेकंड पहले का मान" + error: "त्रुटि:" + formula_valid: "सूत्र मान्य है" + unit: "इकाई:" + unit_hint: "उदा., RPM/s" + description: "विवरण:" + description_hint: "वैकल्पिक विवरण" + available_channels: "उपलब्ध चैनल" + no_channels_available: "कोई चैनल उपलब्ध नहीं - पहले लॉग फ़ाइल लोड करें" + click_to_insert: "सूत्र में डालने के लिए क्लिक करें:" + preview: "पूर्वावलोकन" + min: "न्यूनतम:" + avg: "औसत:" + max: "अधिकतम:" + sample: "नमूना:" + cancel: "रद्द करें" + save: "सहेजें" + validate: "मान्य करें" + +# Update dialog (src/ui/update_dialog.rs) +update: + update_available: "अपडेट उपलब्ध" + new_version_available: "एक नया संस्करण उपलब्ध है!" + current_version: "वर्तमान संस्करण:" + new_version: "नया संस्करण:" + download_size: "डाउनलोड आकार: %{size} MB" + release_notes: "रिलीज़ नोट्स" + download_install: "डाउनलोड और इंस्टॉल करें" + view_on_github: "GitHub पर देखें" + later: "बाद में" + downloading: "अपडेट डाउनलोड हो रहा है..." + please_wait: "कृपया प्रतीक्षा करें..." + download_complete: "डाउनलोड पूर्ण!" + install_instructions: "अपडेट लागू करने के लिए इंस्टॉल पर क्लिक करें।" + windows_restart: "एप्लिकेशन स्वचालित रूप से बंद होकर पुनः आरंभ होगा।" + macos_dmg: "DMG खुलेगा - UltraLog को Applications में खींचें।" + linux_restart: "एप्लिकेशन स्वचालित रूप से बंद होकर पुनः आरंभ होगा।" + install_now: "अभी इंस्टॉल करें" + install_later: "बाद में इंस्टॉल करें" + update_error: "अपडेट त्रुटि" + close: "बंद करें" + update_saved: "अपडेट आपके temp फ़ोल्डर में सहेजा गया।" + +# Toast messages (src/app.rs) +toast: + file_already_loaded: "फ़ाइल पहले से लोड है" + file_loaded: "फ़ाइल सफलतापूर्वक लोड हुई" + max_channels: "अधिकतम 10 चैनल तक पहुँच गए" + channel_already_selected: "चैनल पहले से चयनित है" + channel_from_active_tab: "चैनल सक्रिय टैब की फ़ाइल से होना चाहिए" + no_active_tab: "कोई सक्रिय टैब नहीं" + library_saved: "लाइब्रेरी सहेजी गई" + library_save_failed: "लाइब्रेरी सहेजने में विफल: %{error}" + channel_saved: "चैनल लाइब्रेरी में सहेजा गया" + up_to_date: "आप नवीनतम संस्करण चला रहे हैं" + update_check_failed: "अपडेट जाँच विफल: %{error}" + update_downloaded: "अपडेट सफलतापूर्वक डाउनलोड हुआ" + download_failed: "डाउनलोड विफल: %{error}" + export_png_success: "चार्ट PNG के रूप में निर्यात किया गया" + export_pdf_success: "चार्ट PDF के रूप में निर्यात किया गया" + export_failed: "निर्यात विफल: %{error}" + histogram_exported_png: "हिस्टोग्राम PNG के रूप में निर्यात किया गया" + histogram_exported_pdf: "हिस्टोग्राम PDF के रूप में निर्यात किया गया" + scatter_exported_png: "स्कैटर प्लॉट PNG के रूप में निर्यात किया गया" + scatter_exported_pdf: "स्कैटर प्लॉट PDF के रूप में निर्यात किया गया" + failed_to_save: "सहेजने में विफल: %{error}" + failed_to_apply: "लागू करने में विफल: %{error}" + evaluation_failed: "मूल्यांकन विफल: %{error}" + template_duplicated: "टेम्प्लेट डुप्लिकेट किया गया" + applied_template: "'%{name}' लागू किया गया" + added_to_chart: "'%{name}' चार्ट में जोड़ा गया" + channel_already_on_chart: "चैनल पहले से चार्ट पर है" + max_channels_reached: "अधिकतम 10 चैनल तक पहुँच गए" + analysis_complete: "विश्लेषण पूर्ण: %{name}" + analysis_failed: "विश्लेषण विफल: %{error}" + analyzer_not_found: "विश्लेषक नहीं मिला: %{id}" + no_file_selected: "कोई फ़ाइल चयनित नहीं" + file_not_found: "फ़ाइल नहीं मिली" + +# Common/shared strings +common: + loading: "लोड हो रहा है..." + error: "त्रुटि" + success: "सफल" + warning: "चेतावनी" + info: "जानकारी" + ok: "ठीक है" + yes: "हाँ" + no: "नहीं" + remove: "हटाएं" + add: "जोड़ें" + edit: "संपादित करें" + delete: "मिटाएं" + duplicate: "डुप्लिकेट" + apply: "लागू करें" + close: "बंद करें" + reset: "रीसेट" + +# Computed channels manager (src/ui/computed_channels_manager.rs) +computed: + title: "गणित चैनल" + show_help: "सूत्र सिंटैक्स सहायता दिखाएं" + new: "+ नया" + description: "गणितीय सूत्रों से वर्चुअल चैनल बनाएं।" + quick_create: "त्वरित निर्माण:" + rate_of_change: "परिवर्तन दर" + rate_of_change_hint: "बनाएं: Channel - Channel[-1]" + rate_of_change_desc: "प्रति नमूना परिवर्तन दर" + moving_avg: "चलती औसत" + moving_avg_hint: "बनाएं: 3-नमूना चलती औसत" + moving_avg_desc: "स्मूथिंग के लिए 3-नमूना चलती औसत" + deviation: "% विचलन" + deviation_hint: "बनाएं: लक्ष्य से प्रतिशत विचलन" + deviation_desc: "लक्ष्य मान से प्रतिशत विचलन" + your_library: "आपकी लाइब्रेरी (%{count})" + search: "🔍 खोजें..." + no_channels: "अभी तक कोई गणित चैनल नहीं" + get_started: "त्वरित निर्माण या '+ नया' पर क्लिक करके शुरू करें" + copy: "कॉपी" + builtin_template: "अंतर्निर्मित टेम्प्लेट" + applied_to_file: "वर्तमान फ़ाइल पर लागू (%{count})" + apply_templates_hint: "ऊपर की लाइब्रेरी से टेम्प्लेट लागू करें" + add_chart: "+ चार्ट" + add_to_chart: "चार्ट में जोड़ें" + load_file_hint: "गणित चैनल लागू करने के लिए लॉग फ़ाइल लोड करें" + help_title: "सूत्र सहायता" + help_channel_refs: "चैनल संदर्भ" + help_current_value: "RPM चैनल का वर्तमान मान" + help_quoted_channel: "रिक्त स्थान वाले चैनल (उद्धरण का उपयोग करें)" + help_prev_sample: "पिछला नमूना (इंडेक्स ऑफ़सेट)" + help_samples_ahead: "2 नमूने आगे" + help_time_offset: "100ms पहले का मान (समय ऑफ़सेट)" + help_operators: "ऑपरेटर" + help_basic_math: "बुनियादी गणित" + help_power: "घात (उदा., RPM^2)" + help_grouping: "समूहीकरण" + help_functions: "फ़ंक्शन" + help_trig: "त्रिकोणमिति" + help_sqrt_abs: "वर्गमूल, निरपेक्ष मान" + help_log: "लघुगणक, घातांकी" + help_minmax: "न्यूनतम, अधिकतम" + help_rounding: "राउंडिंग" + help_statistics: "सांख्यिकी (विसंगति पहचान के लिए)" + help_mean: "पूरे RPM चैनल का माध्य" + help_stdev: "मानक विचलन" + help_min_max: "न्यूनतम/अधिकतम मान" + help_examples: "उदाहरण" + help_ex_rate: "प्रति नमूना RPM परिवर्तन" + help_ex_deviation: "AFR % विचलन" + help_ex_zscore: "Z-score" + +# Normalization editor (src/ui/normalization_editor.rs) +normalization: + title: "फ़ील्ड नॉर्मलाइज़ेशन संपादक" + field_mappings: "फ़ील्ड नाम मैपिंग" + reset_defaults: "डिफ़ॉल्ट पर रीसेट करें" + extend_builtin: "अंतर्निर्मित मैपिंग विस्तृत करें" + extend_description: "मौजूदा सामान्यीकृत फ़ील्ड नामों से मैप होने वाले अपने स्रोत नाम जोड़ें।" + source_name: "स्रोत नाम:" + source_hint: "उदा., MyCustomRPM" + maps_to: "इसमें मैप करें:" + select: "चुनें..." + create_new: "नई मैपिंग बनाएं" + create_description: "अपने कस्टम चैनलों के लिए पूरी तरह से नए सामान्यीकृत नाम परिभाषित करें।" + custom_source_hint: "उदा., Sensor_XYZ" + display_as: "इस रूप में प्रदर्शित करें:" + custom_target_hint: "उदा., Custom Sensor" + your_mappings: "आपकी कस्टम मैपिंग" + source: "स्रोत" + builtin_reference: "अंतर्निर्मित मैपिंग संदर्भ" + builtin_description: "ये डिफ़ॉल्ट मैपिंग हैं। आप ऊपर नए स्रोत नाम जोड़कर इन्हें विस्तृत कर सकते हैं।" + +# Scatter plot (src/ui/scatter_plot.rs) +scatter: + no_file_loaded: "स्कैटर प्लॉट का उपयोग करने के लिए लॉग फ़ाइल लोड करें" + x_axis: "X अक्ष:" + y_axis: "Y अक्ष:" + z_axis: "Z अक्ष:" + hits: "हिट्स" + hits_label: "हिट्स:" + remove: "X" + +# Histogram (src/ui/histogram.rs) +histogram: + no_file_loaded: "हिस्टोग्राम का उपयोग करने के लिए लॉग फ़ाइल लोड करें" + x_axis: "X अक्ष:" + y_axis: "Y अक्ष:" + z_axis: "Z अक्ष:" + mode: "मोड:" + average_z: "औसत Z" + hit_count: "हिट गणना" + grid: "ग्रिड:" + +# Chart (src/ui/chart.rs) +chart: + select_channels: "चार्ट प्रदर्शित करने के लिए चैनल चुनें" + +# Analysis panel (src/ui/analysis_panel.rs) +analysis: + window_title: "विश्लेषण टूल्स" + no_file_loaded: "कोई लॉग फ़ाइल लोड नहीं है" + load_file_help: "विश्लेषण टूल्स तक पहुँचने के लिए लॉग फ़ाइल लोड करें।" + category_all: "सभी" + category_filters: "फ़िल्टर" + category_statistics: "सांख्यिकी" + category_afr: "AFR" + category_derived: "व्युत्पन्न" + results_count: "परिणाम (%{count})" + clear_all: "सभी साफ़ करें" + all_tools: "सभी टूल्स" + no_analyzers: "इस श्रेणी में कोई विश्लेषक नहीं" + run_and_chart: "चलाएं और चार्ट करें" + run: "चलाएं" + add_chart: "+ चार्ट" + remove_all_tooltip: "सभी परिणाम हटाएं" + select_valid_channels: "पहले मान्य चैनल चुनें" + run_add_tooltip: "विश्लेषण चलाएं और परिणाम चार्ट में जोड़ें" + run_tooltip: "विश्लेषण चलाएं (बाद में चार्ट में जोड़ें)" + remove_result_tooltip: "परिणाम हटाएं" + add_to_chart_result: "चैनल के रूप में चार्ट में जोड़ें" + +# Tool switcher (src/ui/tool_switcher.rs) +tools: + log_viewer: "लॉग व्यूअर" + scatter_plots: "स्कैटर प्लॉट" + histogram: "हिस्टोग्राम" + +# Activity bar (src/ui/activity_bar.rs) +activity: + files_tooltip: "फ़ाइलें" + channels_tooltip: "चैनल" + tools_tooltip: "टूल्स" + settings_tooltip: "सेटिंग्स" diff --git a/i18n/id.yaml b/i18n/id.yaml new file mode 100644 index 0000000..40b4ba7 --- /dev/null +++ b/i18n/id.yaml @@ -0,0 +1,335 @@ +# Indonesian translations for UltraLog + +# Menu bar (src/ui/menu.rs) +menu: + file: "Berkas" + open_log_file: "Buka Berkas Log..." + close_tab: "Tutup Tab" + export: "Ekspor" + export_png: "Ekspor sebagai PNG..." + export_pdf: "Ekspor sebagai PDF..." + export_histogram_pdf: "Ekspor Histogram sebagai PDF..." + view: "Tampilan" + tool_mode: "Mode Alat" + log_viewer: "Penampil Log" + scatter_plots: "Diagram Sebar" + histogram: "Histogram" + side_panel: "Panel Samping" + files: "Berkas" + channels: "Kanal" + tools: "Alat" + settings: "Pengaturan" + help: "Bantuan" + documentation: "Dokumentasi" + report_issue: "Laporkan Masalah" + support_development: "Dukung Pengembangan" + check_for_updates: "Periksa Pembaruan" + checking_for_updates: "Memeriksa Pembaruan..." + version: "Versi %{version}" + +# Files panel (src/ui/files_panel.rs) +files: + loading: "Memuat %{filename}..." + loaded_files: "Berkas Dimuat (%{count})" + add_file: "Tambah Berkas" + select_file: "Pilih berkas" + or: "atau" + drop_file_here: "Letakkan berkas di sini" + file_info: "%{ecu} • %{channels} kanal • %{points} titik" + +# Channels panel (src/ui/channels_panel.rs) +channels: + computed_channels: "Kanal Komputasi" + computed_channels_tooltip: "Buat kanal virtual dari rumus matematika" + search_hint: "Cari kanal..." + selected_count: "Dipilih: %{selected} / %{max} | Total: %{total}" + no_file_selected: "Tidak ada berkas dipilih" + load_file_to_view: "Muat berkas untuk melihat kanal" + with_data: "Dengan Data (%{count})" + empty: "Kosong (%{count})" + +# Settings panel (src/ui/settings_panel.rs) +settings: + language: "Bahasa" + language_desc: "Pilih bahasa tampilan" + display: "Tampilan" + color_blind_mode: "Mode Buta Warna" + color_blind_desc: "Gunakan palet warna aksesibel (palet Wong)" + font_size: "Ukuran Font:" + cursor_tracking: "Pelacakan Kursor" + cursor_tracking_desc: "Pertahankan kursor di tengah saat menggulir" + window: "Jendela:" + field_names: "Nama Field" + field_normalization: "Normalisasi Field" + field_normalization_desc: "Standarisasi nama kanal antar jenis ECU" + custom_mappings: "%{count} pemetaan kustom" + edit_custom_mappings: "Edit Pemetaan Kustom" + units: "Satuan" + units_desc: "Pilih satuan yang diinginkan untuk tampilan" + temperature: "Suhu:" + pressure: "Tekanan:" + speed: "Kecepatan:" + distance: "Jarak:" + fuel_economy: "Konsumsi BBM:" + volume: "Volume:" + flow_rate: "Laju Aliran:" + acceleration: "Akselerasi:" + updates: "Pembaruan" + check_on_startup: "Periksa saat mulai" + auto_check_desc: "Periksa versi baru secara otomatis" + check_for_updates: "Periksa Pembaruan" + checking: "Memeriksa..." + current_version: "Versi saat ini: %{version}" + update_available: "Pembaruan tersedia: v%{version}" + view_details: "Lihat Detail" + +# Timeline (src/ui/timeline.rs) +timeline: + speed: "Kecepatan:" + time: "Waktu: %{time}" + record: "Rekaman %{current} dari %{total}" + +# Formula editor (src/ui/formula_editor.rs) +formula: + new_computed_channel: "Kanal Komputasi Baru" + edit_computed_channel: "Edit Kanal Komputasi" + name: "Nama:" + name_hint: "contoh: Delta RPM" + formula: "Rumus:" + formula_hint: "contoh: RPM - RPM[-1]" + insert: "Sisipkan:" + insert_tooltip: "Sisipkan %{op}" + prev_sample_tooltip: "Sampel sebelumnya (offset indeks)" + time_ago_tooltip: "Nilai 0,1 detik yang lalu" + error: "Error:" + formula_valid: "Rumus valid" + unit: "Satuan:" + unit_hint: "contoh: RPM/s" + description: "Deskripsi:" + description_hint: "Deskripsi opsional" + available_channels: "Kanal Tersedia" + no_channels_available: "Tidak ada kanal tersedia - muat berkas log terlebih dahulu" + click_to_insert: "Klik untuk menyisipkan ke rumus:" + preview: "Pratinjau" + min: "Min:" + avg: "Rata-rata:" + max: "Maks:" + sample: "Sampel:" + cancel: "Batal" + save: "Simpan" + validate: "Validasi" + +# Update dialog (src/ui/update_dialog.rs) +update: + update_available: "Pembaruan Tersedia" + new_version_available: "Versi baru tersedia!" + current_version: "Versi saat ini:" + new_version: "Versi baru:" + download_size: "Ukuran unduhan: %{size} MB" + release_notes: "Catatan Rilis" + download_install: "Unduh & Pasang" + view_on_github: "Lihat di GitHub" + later: "Nanti" + downloading: "Mengunduh pembaruan..." + please_wait: "Mohon tunggu..." + download_complete: "Unduhan selesai!" + install_instructions: "Klik Pasang untuk menerapkan pembaruan." + windows_restart: "Aplikasi akan ditutup dan dimulai ulang secara otomatis." + macos_dmg: "DMG akan terbuka - seret UltraLog ke Applications." + linux_restart: "Aplikasi akan ditutup dan dimulai ulang secara otomatis." + install_now: "Pasang Sekarang" + install_later: "Pasang Nanti" + update_error: "Error Pembaruan" + close: "Tutup" + update_saved: "Pembaruan disimpan ke folder sementara Anda." + +# Toast messages (src/app.rs) +toast: + file_already_loaded: "Berkas sudah dimuat" + file_loaded: "Berkas berhasil dimuat" + max_channels: "Maksimum 10 kanal tercapai" + channel_already_selected: "Kanal sudah dipilih" + channel_from_active_tab: "Kanal harus berasal dari berkas tab aktif" + no_active_tab: "Tidak ada tab aktif" + library_saved: "Pustaka disimpan" + library_save_failed: "Gagal menyimpan pustaka: %{error}" + channel_saved: "Kanal disimpan ke pustaka" + up_to_date: "Anda menggunakan versi terbaru" + update_check_failed: "Pemeriksaan pembaruan gagal: %{error}" + update_downloaded: "Pembaruan berhasil diunduh" + download_failed: "Unduhan gagal: %{error}" + export_png_success: "Grafik diekspor sebagai PNG" + export_pdf_success: "Grafik diekspor sebagai PDF" + export_failed: "Ekspor gagal: %{error}" + histogram_exported_png: "Histogram diekspor sebagai PNG" + histogram_exported_pdf: "Histogram diekspor sebagai PDF" + scatter_exported_png: "Diagram sebar diekspor sebagai PNG" + scatter_exported_pdf: "Diagram sebar diekspor sebagai PDF" + failed_to_save: "Gagal menyimpan: %{error}" + failed_to_apply: "Gagal menerapkan: %{error}" + evaluation_failed: "Evaluasi gagal: %{error}" + template_duplicated: "Template diduplikasi" + applied_template: "Menerapkan '%{name}'" + added_to_chart: "'%{name}' ditambahkan ke grafik" + channel_already_on_chart: "Kanal sudah ada di grafik" + max_channels_reached: "Maksimum 10 kanal tercapai" + analysis_complete: "Analisis selesai: %{name}" + analysis_failed: "Analisis gagal: %{error}" + analyzer_not_found: "Penganalisis tidak ditemukan: %{id}" + no_file_selected: "Tidak ada berkas dipilih" + file_not_found: "Berkas tidak ditemukan" + +# Common/shared strings +common: + loading: "Memuat..." + error: "Error" + success: "Berhasil" + warning: "Peringatan" + info: "Info" + ok: "OK" + yes: "Ya" + no: "Tidak" + remove: "Hapus" + add: "Tambah" + edit: "Edit" + delete: "Hapus" + duplicate: "Duplikat" + apply: "Terapkan" + close: "Tutup" + reset: "Reset" + +# Computed channels manager (src/ui/computed_channels_manager.rs) +computed: + title: "Kanal Komputasi" + show_help: "Tampilkan bantuan sintaks rumus" + new: "+ Baru" + description: "Buat kanal virtual dari rumus matematika." + quick_create: "Buat Cepat:" + rate_of_change: "Laju Perubahan" + rate_of_change_hint: "Buat: Channel - Channel[-1]" + rate_of_change_desc: "Laju perubahan per sampel" + moving_avg: "Rata-rata Bergerak" + moving_avg_hint: "Buat: rata-rata bergerak 3 sampel" + moving_avg_desc: "Rata-rata bergerak 3 sampel untuk pemulusan" + deviation: "% Deviasi" + deviation_hint: "Buat: Persentase deviasi dari target" + deviation_desc: "Persentase deviasi dari nilai target" + your_library: "Pustaka Anda (%{count})" + search: "🔍 Cari..." + no_channels: "Belum ada kanal komputasi" + get_started: "Gunakan Buat Cepat atau klik '+ Baru' untuk memulai" + copy: "salin" + builtin_template: "Template bawaan" + applied_to_file: "Diterapkan ke Berkas Saat Ini (%{count})" + apply_templates_hint: "Terapkan template dari pustaka di atas" + add_chart: "+ Grafik" + add_to_chart: "Tambahkan ke grafik" + load_file_hint: "Muat berkas log untuk menerapkan kanal komputasi" + help_title: "Bantuan Rumus" + help_channel_refs: "Referensi Kanal" + help_current_value: "Nilai saat ini dari kanal RPM" + help_quoted_channel: "Kanal dengan spasi (gunakan tanda kutip)" + help_prev_sample: "Sampel sebelumnya (offset indeks)" + help_samples_ahead: "2 sampel ke depan" + help_time_offset: "Nilai 100ms yang lalu (offset waktu)" + help_operators: "Operator" + help_basic_math: "Matematika dasar" + help_power: "Pangkat (contoh: RPM^2)" + help_grouping: "Pengelompokan" + help_functions: "Fungsi" + help_trig: "Trigonometri" + help_sqrt_abs: "Akar kuadrat, nilai absolut" + help_log: "Logaritma, eksponensial" + help_minmax: "Minimum, maksimum" + help_rounding: "Pembulatan" + help_statistics: "Statistik (untuk deteksi anomali)" + help_mean: "Rata-rata seluruh kanal RPM" + help_stdev: "Standar deviasi" + help_min_max: "Nilai min/maks" + help_examples: "Contoh" + help_ex_rate: "Perubahan RPM per sampel" + help_ex_deviation: "Deviasi % AFR" + help_ex_zscore: "Z-score" + +# Normalization editor (src/ui/normalization_editor.rs) +normalization: + title: "Editor Normalisasi Field" + field_mappings: "Pemetaan Nama Field" + reset_defaults: "Reset ke Default" + extend_builtin: "Perluas Pemetaan Bawaan" + extend_description: "Tambahkan nama sumber Anda sendiri yang dipetakan ke nama field yang sudah dinormalisasi." + source_name: "Nama Sumber:" + source_hint: "contoh: RPMKustomSaya" + maps_to: "Dipetakan Ke:" + select: "Pilih..." + create_new: "Buat Pemetaan Baru" + create_description: "Tentukan nama normalisasi yang sepenuhnya baru untuk kanal kustom Anda." + custom_source_hint: "contoh: Sensor_XYZ" + display_as: "Tampilkan Sebagai:" + custom_target_hint: "contoh: Sensor Kustom" + your_mappings: "Pemetaan Kustom Anda" + source: "Sumber" + builtin_reference: "Referensi Pemetaan Bawaan" + builtin_description: "Ini adalah pemetaan default. Anda dapat memperluasnya dengan menambahkan nama sumber baru di atas." + +# Scatter plot (src/ui/scatter_plot.rs) +scatter: + no_file_loaded: "Muat berkas log untuk menggunakan diagram sebar" + x_axis: "Sumbu X:" + y_axis: "Sumbu Y:" + z_axis: "Sumbu Z:" + hits: "Hit" + hits_label: "Hit:" + remove: "X" + +# Histogram (src/ui/histogram.rs) +histogram: + no_file_loaded: "Muat berkas log untuk menggunakan histogram" + x_axis: "Sumbu X:" + y_axis: "Sumbu Y:" + z_axis: "Sumbu Z:" + mode: "Mode:" + average_z: "Rata-rata Z" + hit_count: "Jumlah Hit" + grid: "Grid:" + +# Chart (src/ui/chart.rs) +chart: + select_channels: "Pilih kanal untuk menampilkan grafik" + +# Analysis panel (src/ui/analysis_panel.rs) +analysis: + window_title: "Alat Analisis" + no_file_loaded: "Tidak ada berkas log dimuat" + load_file_help: "Muat berkas log untuk mengakses alat analisis." + category_all: "Semua" + category_filters: "Filter" + category_statistics: "Statistik" + category_afr: "AFR" + category_derived: "Turunan" + results_count: "Hasil (%{count})" + clear_all: "Hapus Semua" + all_tools: "Semua Alat" + no_analyzers: "Tidak ada penganalisis dalam kategori ini" + run_and_chart: "Jalankan & Grafik" + run: "Jalankan" + add_chart: "+ Grafik" + remove_all_tooltip: "Hapus semua hasil" + select_valid_channels: "Pilih kanal yang valid terlebih dahulu" + run_add_tooltip: "Jalankan analisis dan tambahkan hasil ke grafik" + run_tooltip: "Jalankan analisis (tambahkan ke grafik nanti)" + remove_result_tooltip: "Hapus hasil" + add_to_chart_result: "Tambahkan ke grafik sebagai kanal" + +# Tool switcher (src/ui/tool_switcher.rs) +tools: + log_viewer: "Penampil Log" + scatter_plots: "Diagram Sebar" + histogram: "Histogram" + +# Activity bar (src/ui/activity_bar.rs) +activity: + files_tooltip: "Berkas" + channels_tooltip: "Kanal" + tools_tooltip: "Alat" + settings_tooltip: "Pengaturan" diff --git a/i18n/it.yaml b/i18n/it.yaml new file mode 100644 index 0000000..4c8d15a --- /dev/null +++ b/i18n/it.yaml @@ -0,0 +1,335 @@ +# Italian translations for UltraLog + +# Menu bar (src/ui/menu.rs) +menu: + file: "File" + open_log_file: "Apri File di Log..." + close_tab: "Chiudi Scheda" + export: "Esporta" + export_png: "Esporta come PNG..." + export_pdf: "Esporta come PDF..." + export_histogram_pdf: "Esporta Istogramma come PDF..." + view: "Visualizza" + tool_mode: "Modalita' Strumento" + log_viewer: "Visualizzatore Log" + scatter_plots: "Grafici a Dispersione" + histogram: "Istogramma" + side_panel: "Pannello Laterale" + files: "File" + channels: "Canali" + tools: "Strumenti" + settings: "Impostazioni" + help: "Aiuto" + documentation: "Documentazione" + report_issue: "Segnala un Problema" + support_development: "Supporta lo Sviluppo" + check_for_updates: "Verifica Aggiornamenti" + checking_for_updates: "Verifica Aggiornamenti in Corso..." + version: "Versione %{version}" + +# Files panel (src/ui/files_panel.rs) +files: + loading: "Caricamento %{filename}..." + loaded_files: "File Caricati (%{count})" + add_file: "Aggiungi File" + select_file: "Seleziona un file" + or: "oppure" + drop_file_here: "Trascina il file qui" + file_info: "%{ecu} • %{channels} canali • %{points} punti" + +# Channels panel (src/ui/channels_panel.rs) +channels: + computed_channels: "Canali Calcolati" + computed_channels_tooltip: "Crea canali virtuali da formule matematiche" + search_hint: "Cerca canali..." + selected_count: "Selezionati: %{selected} / %{max} | Totale: %{total}" + no_file_selected: "Nessun file selezionato" + load_file_to_view: "Carica un file per visualizzare i canali" + with_data: "Con Dati (%{count})" + empty: "Vuoti (%{count})" + +# Settings panel (src/ui/settings_panel.rs) +settings: + language: "Lingua" + language_desc: "Seleziona la lingua di visualizzazione" + display: "Visualizzazione" + color_blind_mode: "Modalita' Daltonici" + color_blind_desc: "Usa una palette di colori accessibile (palette di Wong)" + font_size: "Dimensione Carattere:" + cursor_tracking: "Tracciamento Cursore" + cursor_tracking_desc: "Mantieni il cursore centrato durante lo scorrimento" + window: "Finestra:" + field_names: "Nomi dei Campi" + field_normalization: "Normalizzazione Campi" + field_normalization_desc: "Standardizza i nomi dei canali tra diversi tipi di ECU" + custom_mappings: "%{count} mappature personalizzate" + edit_custom_mappings: "Modifica Mappature Personalizzate" + units: "Unita' di Misura" + units_desc: "Seleziona le unita' preferite per la visualizzazione" + temperature: "Temperatura:" + pressure: "Pressione:" + speed: "Velocita':" + distance: "Distanza:" + fuel_economy: "Consumo Carburante:" + volume: "Volume:" + flow_rate: "Portata:" + acceleration: "Accelerazione:" + updates: "Aggiornamenti" + check_on_startup: "Verifica all'avvio" + auto_check_desc: "Verifica automaticamente la disponibilita' di nuove versioni" + check_for_updates: "Verifica Aggiornamenti" + checking: "Verifica in corso..." + current_version: "Versione attuale: %{version}" + update_available: "Aggiornamento disponibile: v%{version}" + view_details: "Visualizza Dettagli" + +# Timeline (src/ui/timeline.rs) +timeline: + speed: "Velocita':" + time: "Tempo: %{time}" + record: "Record %{current} di %{total}" + +# Formula editor (src/ui/formula_editor.rs) +formula: + new_computed_channel: "Nuovo Canale Calcolato" + edit_computed_channel: "Modifica Canale Calcolato" + name: "Nome:" + name_hint: "es. Delta RPM" + formula: "Formula:" + formula_hint: "es. RPM - RPM[-1]" + insert: "Inserisci:" + insert_tooltip: "Inserisci %{op}" + prev_sample_tooltip: "Campione precedente (offset indice)" + time_ago_tooltip: "Valore di 0,1 secondi fa" + error: "Errore:" + formula_valid: "Formula valida" + unit: "Unita':" + unit_hint: "es. RPM/s" + description: "Descrizione:" + description_hint: "Descrizione opzionale" + available_channels: "Canali Disponibili" + no_channels_available: "Nessun canale disponibile - carica prima un file di log" + click_to_insert: "Clicca per inserire nella formula:" + preview: "Anteprima" + min: "Min:" + avg: "Media:" + max: "Max:" + sample: "Campione:" + cancel: "Annulla" + save: "Salva" + validate: "Valida" + +# Update dialog (src/ui/update_dialog.rs) +update: + update_available: "Aggiornamento Disponibile" + new_version_available: "E' disponibile una nuova versione!" + current_version: "Versione attuale:" + new_version: "Nuova versione:" + download_size: "Dimensione download: %{size} MB" + release_notes: "Note di Rilascio" + download_install: "Scarica e Installa" + view_on_github: "Visualizza su GitHub" + later: "Piu' tardi" + downloading: "Download aggiornamento in corso..." + please_wait: "Attendere prego..." + download_complete: "Download completato!" + install_instructions: "Clicca Installa per applicare l'aggiornamento." + windows_restart: "L'applicazione si chiudera' e si riavviera' automaticamente." + macos_dmg: "Si aprira' il DMG - trascina UltraLog in Applicazioni." + linux_restart: "L'applicazione si chiudera' e si riavviera' automaticamente." + install_now: "Installa Ora" + install_later: "Installa Dopo" + update_error: "Errore di Aggiornamento" + close: "Chiudi" + update_saved: "Aggiornamento salvato nella cartella temporanea." + +# Toast messages (src/app.rs) +toast: + file_already_loaded: "File gia' caricato" + file_loaded: "File caricato con successo" + max_channels: "Raggiunto il limite massimo di 10 canali" + channel_already_selected: "Canale gia' selezionato" + channel_from_active_tab: "Il canale deve appartenere al file della scheda attiva" + no_active_tab: "Nessuna scheda attiva" + library_saved: "Libreria salvata" + library_save_failed: "Salvataggio libreria fallito: %{error}" + channel_saved: "Canale salvato nella libreria" + up_to_date: "Stai utilizzando l'ultima versione" + update_check_failed: "Verifica aggiornamenti fallita: %{error}" + update_downloaded: "Aggiornamento scaricato con successo" + download_failed: "Download fallito: %{error}" + export_png_success: "Grafico esportato come PNG" + export_pdf_success: "Grafico esportato come PDF" + export_failed: "Esportazione fallita: %{error}" + histogram_exported_png: "Istogramma esportato come PNG" + histogram_exported_pdf: "Istogramma esportato come PDF" + scatter_exported_png: "Grafico a dispersione esportato come PNG" + scatter_exported_pdf: "Grafico a dispersione esportato come PDF" + failed_to_save: "Salvataggio fallito: %{error}" + failed_to_apply: "Applicazione fallita: %{error}" + evaluation_failed: "Valutazione fallita: %{error}" + template_duplicated: "Modello duplicato" + applied_template: "Applicato '%{name}'" + added_to_chart: "'%{name}' aggiunto al grafico" + channel_already_on_chart: "Canale gia' presente nel grafico" + max_channels_reached: "Raggiunto il limite massimo di 10 canali" + analysis_complete: "Analisi completata: %{name}" + analysis_failed: "Analisi fallita: %{error}" + analyzer_not_found: "Analizzatore non trovato: %{id}" + no_file_selected: "Nessun file selezionato" + file_not_found: "File non trovato" + +# Common/shared strings +common: + loading: "Caricamento..." + error: "Errore" + success: "Successo" + warning: "Attenzione" + info: "Informazione" + ok: "OK" + yes: "Si'" + no: "No" + remove: "Rimuovi" + add: "Aggiungi" + edit: "Modifica" + delete: "Elimina" + duplicate: "Duplica" + apply: "Applica" + close: "Chiudi" + reset: "Reimposta" + +# Computed channels manager (src/ui/computed_channels_manager.rs) +computed: + title: "Canali Calcolati" + show_help: "Mostra guida alla sintassi delle formule" + new: "+ Nuovo" + description: "Crea canali virtuali da formule matematiche." + quick_create: "Creazione Rapida:" + rate_of_change: "Tasso di Variazione" + rate_of_change_hint: "Crea: Canale - Canale[-1]" + rate_of_change_desc: "Tasso di variazione per campione" + moving_avg: "Media Mobile" + moving_avg_hint: "Crea: media mobile a 3 campioni" + moving_avg_desc: "Media mobile a 3 campioni per livellamento" + deviation: "% Deviazione" + deviation_hint: "Crea: Deviazione percentuale da un obiettivo" + deviation_desc: "Deviazione percentuale dal valore obiettivo" + your_library: "La Tua Libreria (%{count})" + search: "🔍 Cerca..." + no_channels: "Nessun canale calcolato" + get_started: "Usa Creazione Rapida o clicca '+ Nuovo' per iniziare" + copy: "copia" + builtin_template: "Modello predefinito" + applied_to_file: "Applicati al File Corrente (%{count})" + apply_templates_hint: "Applica modelli dalla libreria sopra" + add_chart: "+ Grafico" + add_to_chart: "Aggiungi al grafico" + load_file_hint: "Carica un file di log per applicare i canali calcolati" + help_title: "Guida alle Formule" + help_channel_refs: "Riferimenti ai Canali" + help_current_value: "Valore attuale del canale RPM" + help_quoted_channel: "Canali con spazi (usa le virgolette)" + help_prev_sample: "Campione precedente (offset indice)" + help_samples_ahead: "2 campioni avanti" + help_time_offset: "Valore di 100ms fa (offset temporale)" + help_operators: "Operatori" + help_basic_math: "Matematica base" + help_power: "Potenza (es. RPM^2)" + help_grouping: "Raggruppamento" + help_functions: "Funzioni" + help_trig: "Trigonometria" + help_sqrt_abs: "Radice quadrata, valore assoluto" + help_log: "Logaritmi, esponenziale" + help_minmax: "Minimo, massimo" + help_rounding: "Arrotondamento" + help_statistics: "Statistiche (per rilevamento anomalie)" + help_mean: "Media dell'intero canale RPM" + help_stdev: "Deviazione standard" + help_min_max: "Valori min/max" + help_examples: "Esempi" + help_ex_rate: "Variazione RPM per campione" + help_ex_deviation: "Deviazione % AFR" + help_ex_zscore: "Punteggio Z" + +# Normalization editor (src/ui/normalization_editor.rs) +normalization: + title: "Editor Normalizzazione Campi" + field_mappings: "Mappature Nomi Campi" + reset_defaults: "Ripristina Predefiniti" + extend_builtin: "Estendi Mappature Predefinite" + extend_description: "Aggiungi i tuoi nomi sorgente che mappano a nomi di campo normalizzati esistenti." + source_name: "Nome Sorgente:" + source_hint: "es. MioRPMPersonalizzato" + maps_to: "Mappa A:" + select: "Seleziona..." + create_new: "Crea Nuova Mappatura" + create_description: "Definisci nomi normalizzati completamente nuovi per i tuoi canali personalizzati." + custom_source_hint: "es. Sensore_XYZ" + display_as: "Visualizza Come:" + custom_target_hint: "es. Sensore Personalizzato" + your_mappings: "Le Tue Mappature Personalizzate" + source: "Sorgente" + builtin_reference: "Riferimento Mappature Predefinite" + builtin_description: "Queste sono le mappature predefinite. Puoi estenderle aggiungendo nuovi nomi sorgente sopra." + +# Scatter plot (src/ui/scatter_plot.rs) +scatter: + no_file_loaded: "Carica un file di log per usare i grafici a dispersione" + x_axis: "Asse X:" + y_axis: "Asse Y:" + z_axis: "Asse Z:" + hits: "Occorrenze" + hits_label: "Occorrenze:" + remove: "X" + +# Histogram (src/ui/histogram.rs) +histogram: + no_file_loaded: "Carica un file di log per usare l'istogramma" + x_axis: "Asse X:" + y_axis: "Asse Y:" + z_axis: "Asse Z:" + mode: "Modalita':" + average_z: "Media Z" + hit_count: "Conteggio Occorrenze" + grid: "Griglia:" + +# Chart (src/ui/chart.rs) +chart: + select_channels: "Seleziona i canali per visualizzare il grafico" + +# Analysis panel (src/ui/analysis_panel.rs) +analysis: + window_title: "Strumenti di Analisi" + no_file_loaded: "Nessun file di log caricato" + load_file_help: "Carica un file di log per accedere agli strumenti di analisi." + category_all: "Tutti" + category_filters: "Filtri" + category_statistics: "Statistiche" + category_afr: "AFR" + category_derived: "Derivati" + results_count: "Risultati (%{count})" + clear_all: "Cancella Tutto" + all_tools: "Tutti gli Strumenti" + no_analyzers: "Nessun analizzatore in questa categoria" + run_and_chart: "Esegui e Grafico" + run: "Esegui" + add_chart: "+ Grafico" + remove_all_tooltip: "Rimuovi tutti i risultati" + select_valid_channels: "Seleziona prima canali validi" + run_add_tooltip: "Esegui l'analisi e aggiungi il risultato al grafico" + run_tooltip: "Esegui l'analisi (aggiungi al grafico dopo)" + remove_result_tooltip: "Rimuovi risultato" + add_to_chart_result: "Aggiungi al grafico come canale" + +# Tool switcher (src/ui/tool_switcher.rs) +tools: + log_viewer: "Visualizzatore Log" + scatter_plots: "Grafici a Dispersione" + histogram: "Istogramma" + +# Activity bar (src/ui/activity_bar.rs) +activity: + files_tooltip: "File" + channels_tooltip: "Canali" + tools_tooltip: "Strumenti" + settings_tooltip: "Impostazioni" diff --git a/i18n/ja.yaml b/i18n/ja.yaml new file mode 100644 index 0000000..0251a2b --- /dev/null +++ b/i18n/ja.yaml @@ -0,0 +1,335 @@ +# Japanese translations for UltraLog (日本語) + +# Menu bar (src/ui/menu.rs) +menu: + file: "ファイル" + open_log_file: "ログファイルを開く..." + close_tab: "タブを閉じる" + export: "エクスポート" + export_png: "PNGとしてエクスポート..." + export_pdf: "PDFとしてエクスポート..." + export_histogram_pdf: "ヒストグラムをPDFでエクスポート..." + view: "表示" + tool_mode: "ツールモード" + log_viewer: "ログビューア" + scatter_plots: "散布図" + histogram: "ヒストグラム" + side_panel: "サイドパネル" + files: "ファイル" + channels: "チャンネル" + tools: "ツール" + settings: "設定" + help: "ヘルプ" + documentation: "ドキュメント" + report_issue: "問題を報告" + support_development: "開発を支援" + check_for_updates: "アップデートを確認" + checking_for_updates: "アップデートを確認中..." + version: "バージョン %{version}" + +# Files panel (src/ui/files_panel.rs) +files: + loading: "%{filename} を読み込み中..." + loaded_files: "読み込み済みファイル (%{count})" + add_file: "ファイルを追加" + select_file: "ファイルを選択" + or: "または" + drop_file_here: "ここにファイルをドロップ" + file_info: "%{ecu} • %{channels} ch • %{points} pts" + +# Channels panel (src/ui/channels_panel.rs) +channels: + computed_channels: "計算チャンネル" + computed_channels_tooltip: "数式から仮想チャンネルを作成" + search_hint: "チャンネルを検索..." + selected_count: "選択中: %{selected} / %{max} | 合計: %{total}" + no_file_selected: "ファイルが選択されていません" + load_file_to_view: "チャンネルを表示するにはファイルを読み込んでください" + with_data: "データあり (%{count})" + empty: "空 (%{count})" + +# Settings panel (src/ui/settings_panel.rs) +settings: + language: "言語" + language_desc: "表示言語を選択" + display: "表示" + color_blind_mode: "色覚サポートモード" + color_blind_desc: "アクセシブルなカラーパレットを使用(ウォンのパレット)" + font_size: "フォントサイズ:" + cursor_tracking: "カーソル追従" + cursor_tracking_desc: "スクラブ中にカーソルを中央に保持" + window: "ウィンドウ:" + field_names: "フィールド名" + field_normalization: "フィールド正規化" + field_normalization_desc: "ECUタイプ間でチャンネル名を標準化" + custom_mappings: "カスタムマッピング %{count} 件" + edit_custom_mappings: "カスタムマッピングを編集" + units: "単位" + units_desc: "表示に使用する単位を選択" + temperature: "温度:" + pressure: "圧力:" + speed: "速度:" + distance: "距離:" + fuel_economy: "燃費:" + volume: "容量:" + flow_rate: "流量:" + acceleration: "加速度:" + updates: "アップデート" + check_on_startup: "起動時に確認" + auto_check_desc: "新しいバージョンを自動的に確認" + check_for_updates: "アップデートを確認" + checking: "確認中..." + current_version: "現在のバージョン: %{version}" + update_available: "アップデート可能: v%{version}" + view_details: "詳細を見る" + +# Timeline (src/ui/timeline.rs) +timeline: + speed: "速度:" + time: "時間: %{time}" + record: "レコード %{current} / %{total}" + +# Formula editor (src/ui/formula_editor.rs) +formula: + new_computed_channel: "新規計算チャンネル" + edit_computed_channel: "計算チャンネルを編集" + name: "名前:" + name_hint: "例: RPM Delta" + formula: "数式:" + formula_hint: "例: RPM - RPM[-1]" + insert: "挿入:" + insert_tooltip: "%{op} を挿入" + prev_sample_tooltip: "前のサンプル(インデックスオフセット)" + time_ago_tooltip: "0.1秒前の値" + error: "エラー:" + formula_valid: "数式は有効です" + unit: "単位:" + unit_hint: "例: RPM/s" + description: "説明:" + description_hint: "オプションの説明" + available_channels: "利用可能なチャンネル" + no_channels_available: "チャンネルがありません - 最初にログファイルを読み込んでください" + click_to_insert: "クリックして数式に挿入:" + preview: "プレビュー" + min: "最小:" + avg: "平均:" + max: "最大:" + sample: "サンプル:" + cancel: "キャンセル" + save: "保存" + validate: "検証" + +# Update dialog (src/ui/update_dialog.rs) +update: + update_available: "アップデートがあります" + new_version_available: "新しいバージョンが利用可能です!" + current_version: "現在のバージョン:" + new_version: "新しいバージョン:" + download_size: "ダウンロードサイズ: %{size} MB" + release_notes: "リリースノート" + download_install: "ダウンロードしてインストール" + view_on_github: "GitHubで見る" + later: "後で" + downloading: "アップデートをダウンロード中..." + please_wait: "お待ちください..." + download_complete: "ダウンロード完了!" + install_instructions: "インストールをクリックしてアップデートを適用してください。" + windows_restart: "アプリケーションは自動的に閉じて再起動します。" + macos_dmg: "DMGが開きます - UltraLogをアプリケーションフォルダにドラッグしてください。" + linux_restart: "アプリケーションは自動的に閉じて再起動します。" + install_now: "今すぐインストール" + install_later: "後でインストール" + update_error: "アップデートエラー" + close: "閉じる" + update_saved: "アップデートは一時フォルダに保存されました。" + +# Toast messages (src/app.rs) +toast: + file_already_loaded: "ファイルは既に読み込まれています" + file_loaded: "ファイルの読み込みに成功しました" + max_channels: "チャンネルの上限(10個)に達しました" + channel_already_selected: "チャンネルは既に選択されています" + channel_from_active_tab: "チャンネルはアクティブなタブのファイルから選択してください" + no_active_tab: "アクティブなタブがありません" + library_saved: "ライブラリを保存しました" + library_save_failed: "ライブラリの保存に失敗しました: %{error}" + channel_saved: "チャンネルをライブラリに保存しました" + up_to_date: "最新バージョンを使用中です" + update_check_failed: "アップデートの確認に失敗しました: %{error}" + update_downloaded: "アップデートのダウンロードに成功しました" + download_failed: "ダウンロードに失敗しました: %{error}" + export_png_success: "チャートをPNGとしてエクスポートしました" + export_pdf_success: "チャートをPDFとしてエクスポートしました" + export_failed: "エクスポートに失敗しました: %{error}" + histogram_exported_png: "ヒストグラムをPNGとしてエクスポートしました" + histogram_exported_pdf: "ヒストグラムをPDFとしてエクスポートしました" + scatter_exported_png: "散布図をPNGとしてエクスポートしました" + scatter_exported_pdf: "散布図をPDFとしてエクスポートしました" + failed_to_save: "保存に失敗しました: %{error}" + failed_to_apply: "適用に失敗しました: %{error}" + evaluation_failed: "評価に失敗しました: %{error}" + template_duplicated: "テンプレートを複製しました" + applied_template: "'%{name}' を適用しました" + added_to_chart: "'%{name}' をチャートに追加しました" + channel_already_on_chart: "チャンネルは既にチャートに表示されています" + max_channels_reached: "チャンネルの上限(10個)に達しました" + analysis_complete: "分析完了: %{name}" + analysis_failed: "分析に失敗しました: %{error}" + analyzer_not_found: "アナライザーが見つかりません: %{id}" + no_file_selected: "ファイルが選択されていません" + file_not_found: "ファイルが見つかりません" + +# Common/shared strings +common: + loading: "読み込み中..." + error: "エラー" + success: "成功" + warning: "警告" + info: "情報" + ok: "OK" + yes: "はい" + no: "いいえ" + remove: "削除" + add: "追加" + edit: "編集" + delete: "削除" + duplicate: "複製" + apply: "適用" + close: "閉じる" + reset: "リセット" + +# Computed channels manager (src/ui/computed_channels_manager.rs) +computed: + title: "計算チャンネル" + show_help: "数式構文のヘルプを表示" + new: "+ 新規" + description: "数式から仮想チャンネルを作成します。" + quick_create: "クイック作成:" + rate_of_change: "変化率" + rate_of_change_hint: "作成: Channel - Channel[-1]" + rate_of_change_desc: "サンプルあたりの変化率" + moving_avg: "移動平均" + moving_avg_hint: "作成: 3サンプル移動平均" + moving_avg_desc: "スムージング用3サンプル移動平均" + deviation: "偏差%" + deviation_hint: "作成: 目標値からのパーセンテージ偏差" + deviation_desc: "目標値からのパーセンテージ偏差" + your_library: "ライブラリ (%{count})" + search: "検索..." + no_channels: "計算チャンネルはまだありません" + get_started: "クイック作成を使用するか、'+ 新規' をクリックして開始してください" + copy: "コピー" + builtin_template: "組み込みテンプレート" + applied_to_file: "現在のファイルに適用済み (%{count})" + apply_templates_hint: "上記のライブラリからテンプレートを適用してください" + add_chart: "+ チャート" + add_to_chart: "チャートに追加" + load_file_hint: "計算チャンネルを適用するにはログファイルを読み込んでください" + help_title: "数式ヘルプ" + help_channel_refs: "チャンネル参照" + help_current_value: "RPMチャンネルの現在の値" + help_quoted_channel: "スペースを含むチャンネル(引用符を使用)" + help_prev_sample: "前のサンプル(インデックスオフセット)" + help_samples_ahead: "2サンプル先" + help_time_offset: "100ミリ秒前の値(時間オフセット)" + help_operators: "演算子" + help_basic_math: "基本演算" + help_power: "べき乗(例: RPM^2)" + help_grouping: "グループ化" + help_functions: "関数" + help_trig: "三角関数" + help_sqrt_abs: "平方根、絶対値" + help_log: "対数、指数" + help_minmax: "最小値、最大値" + help_rounding: "丸め" + help_statistics: "統計(異常検出用)" + help_mean: "RPMチャンネル全体の平均" + help_stdev: "標準偏差" + help_min_max: "最小/最大値" + help_examples: "例" + help_ex_rate: "サンプルあたりのRPM変化" + help_ex_deviation: "AFR偏差%" + help_ex_zscore: "Z-score" + +# Normalization editor (src/ui/normalization_editor.rs) +normalization: + title: "フィールド正規化エディタ" + field_mappings: "フィールド名マッピング" + reset_defaults: "デフォルトにリセット" + extend_builtin: "組み込みマッピングを拡張" + extend_description: "既存の正規化フィールド名にマッピングする独自のソース名を追加します。" + source_name: "ソース名:" + source_hint: "例: MyCustomRPM" + maps_to: "マッピング先:" + select: "選択..." + create_new: "新規マッピングを作成" + create_description: "カスタムチャンネル用に完全に新しい正規化名を定義します。" + custom_source_hint: "例: Sensor_XYZ" + display_as: "表示名:" + custom_target_hint: "例: カスタムセンサー" + your_mappings: "カスタムマッピング" + source: "ソース" + builtin_reference: "組み込みマッピング参照" + builtin_description: "これらはデフォルトのマッピングです。上記で新しいソース名を追加して拡張できます。" + +# Scatter plot (src/ui/scatter_plot.rs) +scatter: + no_file_loaded: "散布図を使用するにはログファイルを読み込んでください" + x_axis: "X軸:" + y_axis: "Y軸:" + z_axis: "Z軸:" + hits: "ヒット数" + hits_label: "ヒット数:" + remove: "X" + +# Histogram (src/ui/histogram.rs) +histogram: + no_file_loaded: "ヒストグラムを使用するにはログファイルを読み込んでください" + x_axis: "X軸:" + y_axis: "Y軸:" + z_axis: "Z軸:" + mode: "モード:" + average_z: "Z平均" + hit_count: "ヒット数" + grid: "グリッド:" + +# Chart (src/ui/chart.rs) +chart: + select_channels: "チャートを表示するにはチャンネルを選択してください" + +# Analysis panel (src/ui/analysis_panel.rs) +analysis: + window_title: "分析ツール" + no_file_loaded: "ログファイルが読み込まれていません" + load_file_help: "分析ツールにアクセスするにはログファイルを読み込んでください。" + category_all: "すべて" + category_filters: "フィルター" + category_statistics: "統計" + category_afr: "AFR" + category_derived: "派生" + results_count: "結果 (%{count})" + clear_all: "すべてクリア" + all_tools: "すべてのツール" + no_analyzers: "このカテゴリにはアナライザーがありません" + run_and_chart: "実行してチャートへ" + run: "実行" + add_chart: "+ チャート" + remove_all_tooltip: "すべての結果を削除" + select_valid_channels: "最初に有効なチャンネルを選択してください" + run_add_tooltip: "分析を実行して結果をチャートに追加" + run_tooltip: "分析を実行(後でチャートに追加)" + remove_result_tooltip: "結果を削除" + add_to_chart_result: "チャンネルとしてチャートに追加" + +# Tool switcher (src/ui/tool_switcher.rs) +tools: + log_viewer: "ログビューア" + scatter_plots: "散布図" + histogram: "ヒストグラム" + +# Activity bar (src/ui/activity_bar.rs) +activity: + files_tooltip: "ファイル" + channels_tooltip: "チャンネル" + tools_tooltip: "ツール" + settings_tooltip: "設定" diff --git a/i18n/pt-BR.yaml b/i18n/pt-BR.yaml new file mode 100644 index 0000000..9ebb278 --- /dev/null +++ b/i18n/pt-BR.yaml @@ -0,0 +1,335 @@ +# Traduções em Português Brasileiro para UltraLog + +# Barra de menu (src/ui/menu.rs) +menu: + file: "Arquivo" + open_log_file: "Abrir Arquivo de Log..." + close_tab: "Fechar Aba" + export: "Exportar" + export_png: "Exportar como PNG..." + export_pdf: "Exportar como PDF..." + export_histogram_pdf: "Exportar Histograma como PDF..." + view: "Visualizar" + tool_mode: "Modo de Ferramenta" + log_viewer: "Visualizador de Logs" + scatter_plots: "Gráficos de Dispersão" + histogram: "Histograma" + side_panel: "Painel Lateral" + files: "Arquivos" + channels: "Canais" + tools: "Ferramentas" + settings: "Configurações" + help: "Ajuda" + documentation: "Documentação" + report_issue: "Reportar Problema" + support_development: "Apoiar o Desenvolvimento" + check_for_updates: "Verificar Atualizações" + checking_for_updates: "Verificando Atualizações..." + version: "Versão %{version}" + +# Painel de arquivos (src/ui/files_panel.rs) +files: + loading: "Carregando %{filename}..." + loaded_files: "Arquivos Carregados (%{count})" + add_file: "Adicionar Arquivo" + select_file: "Selecionar um arquivo" + or: "ou" + drop_file_here: "Arraste o arquivo aqui" + file_info: "%{ecu} • %{channels} can • %{points} pts" + +# Painel de canais (src/ui/channels_panel.rs) +channels: + computed_channels: "Canais Calculados" + computed_channels_tooltip: "Criar canais virtuais a partir de fórmulas matemáticas" + search_hint: "Buscar canais..." + selected_count: "Selecionados: %{selected} / %{max} | Total: %{total}" + no_file_selected: "Nenhum arquivo selecionado" + load_file_to_view: "Carregue um arquivo para visualizar os canais" + with_data: "Com Dados (%{count})" + empty: "Vazios (%{count})" + +# Painel de configurações (src/ui/settings_panel.rs) +settings: + language: "Idioma" + language_desc: "Selecionar idioma de exibição" + display: "Exibição" + color_blind_mode: "Modo para Daltonismo" + color_blind_desc: "Usar paleta de cores acessível (paleta de Wong)" + font_size: "Tamanho da Fonte:" + cursor_tracking: "Rastreamento do Cursor" + cursor_tracking_desc: "Manter o cursor centralizado durante a navegação" + window: "Janela:" + field_names: "Nomes dos Campos" + field_normalization: "Normalização de Campos" + field_normalization_desc: "Padronizar nomes de canais entre tipos de ECU" + custom_mappings: "%{count} mapeamentos personalizados" + edit_custom_mappings: "Editar Mapeamentos Personalizados" + units: "Unidades" + units_desc: "Selecionar unidades preferidas para exibição" + temperature: "Temperatura:" + pressure: "Pressão:" + speed: "Velocidade:" + distance: "Distância:" + fuel_economy: "Economia de Combustível:" + volume: "Volume:" + flow_rate: "Vazão:" + acceleration: "Aceleração:" + updates: "Atualizações" + check_on_startup: "Verificar ao iniciar" + auto_check_desc: "Verificar automaticamente por novas versões" + check_for_updates: "Verificar Atualizações" + checking: "Verificando..." + current_version: "Versão atual: %{version}" + update_available: "Atualização disponível: v%{version}" + view_details: "Ver Detalhes" + +# Linha do tempo (src/ui/timeline.rs) +timeline: + speed: "Velocidade:" + time: "Tempo: %{time}" + record: "Registro %{current} de %{total}" + +# Editor de fórmulas (src/ui/formula_editor.rs) +formula: + new_computed_channel: "Novo Canal Calculado" + edit_computed_channel: "Editar Canal Calculado" + name: "Nome:" + name_hint: "ex.: Delta de RPM" + formula: "Fórmula:" + formula_hint: "ex.: RPM - RPM[-1]" + insert: "Inserir:" + insert_tooltip: "Inserir %{op}" + prev_sample_tooltip: "Amostra anterior (deslocamento de índice)" + time_ago_tooltip: "Valor de 0,1 segundos atrás" + error: "Erro:" + formula_valid: "Fórmula válida" + unit: "Unidade:" + unit_hint: "ex.: RPM/s" + description: "Descrição:" + description_hint: "Descrição opcional" + available_channels: "Canais Disponíveis" + no_channels_available: "Nenhum canal disponível - carregue um arquivo de log primeiro" + click_to_insert: "Clique para inserir na fórmula:" + preview: "Pré-visualização" + min: "Mín:" + avg: "Méd:" + max: "Máx:" + sample: "Amostra:" + cancel: "Cancelar" + save: "Salvar" + validate: "Validar" + +# Diálogo de atualização (src/ui/update_dialog.rs) +update: + update_available: "Atualização Disponível" + new_version_available: "Uma nova versão está disponível!" + current_version: "Versão atual:" + new_version: "Nova versão:" + download_size: "Tamanho do download: %{size} MB" + release_notes: "Notas da Versão" + download_install: "Baixar e Instalar" + view_on_github: "Ver no GitHub" + later: "Depois" + downloading: "Baixando atualização..." + please_wait: "Por favor, aguarde..." + download_complete: "Download concluído!" + install_instructions: "Clique em Instalar para aplicar a atualização." + windows_restart: "O aplicativo será fechado e reiniciado automaticamente." + macos_dmg: "O DMG será aberto - arraste o UltraLog para Aplicativos." + linux_restart: "O aplicativo será fechado e reiniciado automaticamente." + install_now: "Instalar Agora" + install_later: "Instalar Depois" + update_error: "Erro na Atualização" + close: "Fechar" + update_saved: "Atualização salva na pasta temporária." + +# Mensagens toast (src/app.rs) +toast: + file_already_loaded: "Arquivo já carregado" + file_loaded: "Arquivo carregado com sucesso" + max_channels: "Máximo de 10 canais atingido" + channel_already_selected: "Canal já selecionado" + channel_from_active_tab: "O canal deve ser da aba ativa" + no_active_tab: "Nenhuma aba ativa" + library_saved: "Biblioteca salva" + library_save_failed: "Falha ao salvar biblioteca: %{error}" + channel_saved: "Canal salvo na biblioteca" + up_to_date: "Você está usando a versão mais recente" + update_check_failed: "Falha ao verificar atualização: %{error}" + update_downloaded: "Atualização baixada com sucesso" + download_failed: "Falha no download: %{error}" + export_png_success: "Gráfico exportado como PNG" + export_pdf_success: "Gráfico exportado como PDF" + export_failed: "Falha na exportação: %{error}" + histogram_exported_png: "Histograma exportado como PNG" + histogram_exported_pdf: "Histograma exportado como PDF" + scatter_exported_png: "Gráfico de dispersão exportado como PNG" + scatter_exported_pdf: "Gráfico de dispersão exportado como PDF" + failed_to_save: "Falha ao salvar: %{error}" + failed_to_apply: "Falha ao aplicar: %{error}" + evaluation_failed: "Falha na avaliação: %{error}" + template_duplicated: "Modelo duplicado" + applied_template: "Aplicado '%{name}'" + added_to_chart: "'%{name}' adicionado ao gráfico" + channel_already_on_chart: "Canal já está no gráfico" + max_channels_reached: "Máximo de 10 canais atingido" + analysis_complete: "Análise concluída: %{name}" + analysis_failed: "Falha na análise: %{error}" + analyzer_not_found: "Analisador não encontrado: %{id}" + no_file_selected: "Nenhum arquivo selecionado" + file_not_found: "Arquivo não encontrado" + +# Strings comuns/compartilhadas +common: + loading: "Carregando..." + error: "Erro" + success: "Sucesso" + warning: "Aviso" + info: "Informação" + ok: "OK" + yes: "Sim" + no: "Não" + remove: "Remover" + add: "Adicionar" + edit: "Editar" + delete: "Excluir" + duplicate: "Duplicar" + apply: "Aplicar" + close: "Fechar" + reset: "Redefinir" + +# Gerenciador de canais calculados (src/ui/computed_channels_manager.rs) +computed: + title: "Canais Calculados" + show_help: "Mostrar ajuda de sintaxe de fórmulas" + new: "+ Novo" + description: "Crie canais virtuais a partir de fórmulas matemáticas." + quick_create: "Criação Rápida:" + rate_of_change: "Taxa de Variação" + rate_of_change_hint: "Criar: Canal - Canal[-1]" + rate_of_change_desc: "Taxa de variação por amostra" + moving_avg: "Média Móvel" + moving_avg_hint: "Criar: média móvel de 3 amostras" + moving_avg_desc: "Média móvel de 3 amostras para suavização" + deviation: "% Desvio" + deviation_hint: "Criar: Desvio percentual de um alvo" + deviation_desc: "Desvio percentual do valor alvo" + your_library: "Sua Biblioteca (%{count})" + search: "Buscar..." + no_channels: "Nenhum canal calculado ainda" + get_started: "Use Criação Rápida ou clique em '+ Novo' para começar" + copy: "copiar" + builtin_template: "Modelo integrado" + applied_to_file: "Aplicado ao Arquivo Atual (%{count})" + apply_templates_hint: "Aplicar modelos da biblioteca acima" + add_chart: "+ Gráfico" + add_to_chart: "Adicionar ao gráfico" + load_file_hint: "Carregue um arquivo de log para aplicar canais calculados" + help_title: "Ajuda de Fórmulas" + help_channel_refs: "Referências de Canais" + help_current_value: "Valor atual do canal RPM" + help_quoted_channel: "Canais com espaços (use aspas)" + help_prev_sample: "Amostra anterior (deslocamento de índice)" + help_samples_ahead: "2 amostras à frente" + help_time_offset: "Valor de 100ms atrás (deslocamento de tempo)" + help_operators: "Operadores" + help_basic_math: "Matemática básica" + help_power: "Potência (ex.: RPM^2)" + help_grouping: "Agrupamento" + help_functions: "Funções" + help_trig: "Trigonometria" + help_sqrt_abs: "Raiz quadrada, valor absoluto" + help_log: "Logaritmos, exponencial" + help_minmax: "Mínimo, máximo" + help_rounding: "Arredondamento" + help_statistics: "Estatísticas (para detecção de anomalias)" + help_mean: "Média de todo o canal RPM" + help_stdev: "Desvio padrão" + help_min_max: "Valores mín/máx" + help_examples: "Exemplos" + help_ex_rate: "Variação de RPM por amostra" + help_ex_deviation: "% de desvio de AFR" + help_ex_zscore: "Z-score" + +# Editor de normalização (src/ui/normalization_editor.rs) +normalization: + title: "Editor de Normalização de Campos" + field_mappings: "Mapeamentos de Nomes de Campos" + reset_defaults: "Restaurar Padrões" + extend_builtin: "Estender Mapeamentos Integrados" + extend_description: "Adicione seus próprios nomes de origem que mapeiam para nomes de campos normalizados existentes." + source_name: "Nome de Origem:" + source_hint: "ex.: MeuRPMPersonalizado" + maps_to: "Mapeia Para:" + select: "Selecionar..." + create_new: "Criar Novo Mapeamento" + create_description: "Defina nomes normalizados completamente novos para seus canais personalizados." + custom_source_hint: "ex.: Sensor_XYZ" + display_as: "Exibir Como:" + custom_target_hint: "ex.: Sensor Personalizado" + your_mappings: "Seus Mapeamentos Personalizados" + source: "Origem" + builtin_reference: "Referência de Mapeamentos Integrados" + builtin_description: "Estes são os mapeamentos padrão. Você pode estendê-los adicionando novos nomes de origem acima." + +# Gráfico de dispersão (src/ui/scatter_plot.rs) +scatter: + no_file_loaded: "Carregue um arquivo de log para usar gráficos de dispersão" + x_axis: "Eixo X:" + y_axis: "Eixo Y:" + z_axis: "Eixo Z:" + hits: "Ocorrências" + hits_label: "Ocorrências:" + remove: "X" + +# Histograma (src/ui/histogram.rs) +histogram: + no_file_loaded: "Carregue um arquivo de log para usar o histograma" + x_axis: "Eixo X:" + y_axis: "Eixo Y:" + z_axis: "Eixo Z:" + mode: "Modo:" + average_z: "Média Z" + hit_count: "Contagem de Ocorrências" + grid: "Grade:" + +# Gráfico (src/ui/chart.rs) +chart: + select_channels: "Selecione canais para exibir o gráfico" + +# Painel de análise (src/ui/analysis_panel.rs) +analysis: + window_title: "Ferramentas de Análise" + no_file_loaded: "Nenhum arquivo de log carregado" + load_file_help: "Carregue um arquivo de log para acessar as ferramentas de análise." + category_all: "Todos" + category_filters: "Filtros" + category_statistics: "Estatísticas" + category_afr: "AFR" + category_derived: "Derivados" + results_count: "Resultados (%{count})" + clear_all: "Limpar Tudo" + all_tools: "Todas as Ferramentas" + no_analyzers: "Nenhum analisador nesta categoria" + run_and_chart: "Executar e Gráfico" + run: "Executar" + add_chart: "+ Gráfico" + remove_all_tooltip: "Remover todos os resultados" + select_valid_channels: "Selecione canais válidos primeiro" + run_add_tooltip: "Executar análise e adicionar resultado ao gráfico" + run_tooltip: "Executar análise (adicionar ao gráfico depois)" + remove_result_tooltip: "Remover resultado" + add_to_chart_result: "Adicionar ao gráfico como canal" + +# Alternador de ferramentas (src/ui/tool_switcher.rs) +tools: + log_viewer: "Visualizador de Logs" + scatter_plots: "Gráficos de Dispersão" + histogram: "Histograma" + +# Barra de atividades (src/ui/activity_bar.rs) +activity: + files_tooltip: "Arquivos" + channels_tooltip: "Canais" + tools_tooltip: "Ferramentas" + settings_tooltip: "Configurações" diff --git a/i18n/pt-PT.yaml b/i18n/pt-PT.yaml new file mode 100644 index 0000000..945848b --- /dev/null +++ b/i18n/pt-PT.yaml @@ -0,0 +1,335 @@ +# Traduções em Português Europeu para UltraLog + +# Menu bar (src/ui/menu.rs) +menu: + file: "Ficheiro" + open_log_file: "Abrir Ficheiro de Registo..." + close_tab: "Fechar Separador" + export: "Exportar" + export_png: "Exportar como PNG..." + export_pdf: "Exportar como PDF..." + export_histogram_pdf: "Exportar Histograma como PDF..." + view: "Ver" + tool_mode: "Modo de Ferramenta" + log_viewer: "Visualizador de Registos" + scatter_plots: "Gráficos de Dispersão" + histogram: "Histograma" + side_panel: "Painel Lateral" + files: "Ficheiros" + channels: "Canais" + tools: "Ferramentas" + settings: "Definições" + help: "Ajuda" + documentation: "Documentação" + report_issue: "Reportar Problema" + support_development: "Apoiar Desenvolvimento" + check_for_updates: "Verificar Atualizações" + checking_for_updates: "A Verificar Atualizações..." + version: "Versão %{version}" + +# Files panel (src/ui/files_panel.rs) +files: + loading: "A carregar %{filename}..." + loaded_files: "Ficheiros Carregados (%{count})" + add_file: "Adicionar Ficheiro" + select_file: "Selecionar um ficheiro" + or: "ou" + drop_file_here: "Largar ficheiro aqui" + file_info: "%{ecu} • %{channels} ch • %{points} pts" + +# Channels panel (src/ui/channels_panel.rs) +channels: + computed_channels: "Canais Calculados" + computed_channels_tooltip: "Criar canais virtuais a partir de fórmulas matemáticas" + search_hint: "Pesquisar canais..." + selected_count: "Selecionados: %{selected} / %{max} | Total: %{total}" + no_file_selected: "Nenhum ficheiro selecionado" + load_file_to_view: "Carregue um ficheiro para ver os canais" + with_data: "Com Dados (%{count})" + empty: "Vazios (%{count})" + +# Settings panel (src/ui/settings_panel.rs) +settings: + language: "Idioma" + language_desc: "Selecionar idioma de apresentação" + display: "Visualização" + color_blind_mode: "Modo Daltonismo" + color_blind_desc: "Usar paleta de cores acessível (paleta de Wong)" + font_size: "Tamanho da Letra:" + cursor_tracking: "Seguimento do Cursor" + cursor_tracking_desc: "Manter o cursor centrado durante a navegação" + window: "Janela:" + field_names: "Nomes dos Campos" + field_normalization: "Normalização de Campos" + field_normalization_desc: "Uniformizar nomes de canais entre tipos de ECU" + custom_mappings: "%{count} mapeamentos personalizados" + edit_custom_mappings: "Editar Mapeamentos Personalizados" + units: "Unidades" + units_desc: "Selecionar unidades preferidas para apresentação" + temperature: "Temperatura:" + pressure: "Pressão:" + speed: "Velocidade:" + distance: "Distância:" + fuel_economy: "Consumo de Combustível:" + volume: "Volume:" + flow_rate: "Caudal:" + acceleration: "Aceleração:" + updates: "Atualizações" + check_on_startup: "Verificar ao iniciar" + auto_check_desc: "Verificar automaticamente novas versões" + check_for_updates: "Verificar Atualizações" + checking: "A verificar..." + current_version: "Versão atual: %{version}" + update_available: "Atualização disponível: v%{version}" + view_details: "Ver Detalhes" + +# Timeline (src/ui/timeline.rs) +timeline: + speed: "Velocidade:" + time: "Tempo: %{time}" + record: "Registo %{current} de %{total}" + +# Formula editor (src/ui/formula_editor.rs) +formula: + new_computed_channel: "Novo Canal Calculado" + edit_computed_channel: "Editar Canal Calculado" + name: "Nome:" + name_hint: "ex., Delta de RPM" + formula: "Fórmula:" + formula_hint: "ex., RPM - RPM[-1]" + insert: "Inserir:" + insert_tooltip: "Inserir %{op}" + prev_sample_tooltip: "Amostra anterior (deslocamento de índice)" + time_ago_tooltip: "Valor há 0,1 segundos" + error: "Erro:" + formula_valid: "Fórmula válida" + unit: "Unidade:" + unit_hint: "ex., RPM/s" + description: "Descrição:" + description_hint: "Descrição opcional" + available_channels: "Canais Disponíveis" + no_channels_available: "Nenhum canal disponível - carregue primeiro um ficheiro de registo" + click_to_insert: "Clique para inserir na fórmula:" + preview: "Pré-visualização" + min: "Mín:" + avg: "Méd:" + max: "Máx:" + sample: "Amostra:" + cancel: "Cancelar" + save: "Guardar" + validate: "Validar" + +# Update dialog (src/ui/update_dialog.rs) +update: + update_available: "Atualização Disponível" + new_version_available: "Está disponível uma nova versão!" + current_version: "Versão atual:" + new_version: "Nova versão:" + download_size: "Tamanho do download: %{size} MB" + release_notes: "Notas de Lançamento" + download_install: "Transferir e Instalar" + view_on_github: "Ver no GitHub" + later: "Mais Tarde" + downloading: "A transferir atualização..." + please_wait: "Por favor aguarde..." + download_complete: "Transferência concluída!" + install_instructions: "Clique em Instalar para aplicar a atualização." + windows_restart: "A aplicação irá fechar e reiniciar automaticamente." + macos_dmg: "O DMG será aberto - arraste o UltraLog para Aplicações." + linux_restart: "A aplicação irá fechar e reiniciar automaticamente." + install_now: "Instalar Agora" + install_later: "Instalar Mais Tarde" + update_error: "Erro de Atualização" + close: "Fechar" + update_saved: "Atualização guardada na pasta temporária." + +# Toast messages (src/app.rs) +toast: + file_already_loaded: "Ficheiro já carregado" + file_loaded: "Ficheiro carregado com sucesso" + max_channels: "Máximo de 10 canais atingido" + channel_already_selected: "Canal já selecionado" + channel_from_active_tab: "O canal deve ser do ficheiro do separador ativo" + no_active_tab: "Nenhum separador ativo" + library_saved: "Biblioteca guardada" + library_save_failed: "Falha ao guardar biblioteca: %{error}" + channel_saved: "Canal guardado na biblioteca" + up_to_date: "Está a utilizar a versão mais recente" + update_check_failed: "Falha na verificação de atualizações: %{error}" + update_downloaded: "Atualização transferida com sucesso" + download_failed: "Falha na transferência: %{error}" + export_png_success: "Gráfico exportado como PNG" + export_pdf_success: "Gráfico exportado como PDF" + export_failed: "Falha na exportação: %{error}" + histogram_exported_png: "Histograma exportado como PNG" + histogram_exported_pdf: "Histograma exportado como PDF" + scatter_exported_png: "Gráfico de dispersão exportado como PNG" + scatter_exported_pdf: "Gráfico de dispersão exportado como PDF" + failed_to_save: "Falha ao guardar: %{error}" + failed_to_apply: "Falha ao aplicar: %{error}" + evaluation_failed: "Falha na avaliação: %{error}" + template_duplicated: "Modelo duplicado" + applied_template: "'%{name}' aplicado" + added_to_chart: "'%{name}' adicionado ao gráfico" + channel_already_on_chart: "Canal já no gráfico" + max_channels_reached: "Máximo de 10 canais atingido" + analysis_complete: "Análise concluída: %{name}" + analysis_failed: "Falha na análise: %{error}" + analyzer_not_found: "Analisador não encontrado: %{id}" + no_file_selected: "Nenhum ficheiro selecionado" + file_not_found: "Ficheiro não encontrado" + +# Common/shared strings +common: + loading: "A carregar..." + error: "Erro" + success: "Sucesso" + warning: "Aviso" + info: "Informação" + ok: "OK" + yes: "Sim" + no: "Não" + remove: "Remover" + add: "Adicionar" + edit: "Editar" + delete: "Eliminar" + duplicate: "Duplicar" + apply: "Aplicar" + close: "Fechar" + reset: "Repor" + +# Computed channels manager (src/ui/computed_channels_manager.rs) +computed: + title: "Canais Calculados" + show_help: "Mostrar ajuda de sintaxe de fórmulas" + new: "+ Novo" + description: "Criar canais virtuais a partir de fórmulas matemáticas." + quick_create: "Criação Rápida:" + rate_of_change: "Taxa de Variação" + rate_of_change_hint: "Criar: Canal - Canal[-1]" + rate_of_change_desc: "Taxa de variação por amostra" + moving_avg: "Média Móvel" + moving_avg_hint: "Criar: média móvel de 3 amostras" + moving_avg_desc: "Média móvel de 3 amostras para suavização" + deviation: "% Desvio" + deviation_hint: "Criar: Desvio percentual de um objetivo" + deviation_desc: "Desvio percentual do valor objetivo" + your_library: "A Sua Biblioteca (%{count})" + search: "Pesquisar..." + no_channels: "Ainda não há canais calculados" + get_started: "Use Criação Rápida ou clique em '+ Novo' para começar" + copy: "copiar" + builtin_template: "Modelo incorporado" + applied_to_file: "Aplicado ao Ficheiro Atual (%{count})" + apply_templates_hint: "Aplicar modelos da biblioteca acima" + add_chart: "+ Gráfico" + add_to_chart: "Adicionar ao gráfico" + load_file_hint: "Carregue um ficheiro de registo para aplicar canais calculados" + help_title: "Ajuda de Fórmulas" + help_channel_refs: "Referências de Canais" + help_current_value: "Valor atual do canal RPM" + help_quoted_channel: "Canais com espaços (usar aspas)" + help_prev_sample: "Amostra anterior (deslocamento de índice)" + help_samples_ahead: "2 amostras à frente" + help_time_offset: "Valor há 100ms (deslocamento temporal)" + help_operators: "Operadores" + help_basic_math: "Matemática básica" + help_power: "Potência (ex., RPM^2)" + help_grouping: "Agrupamento" + help_functions: "Funções" + help_trig: "Trigonometria" + help_sqrt_abs: "Raiz quadrada, valor absoluto" + help_log: "Logaritmos, exponencial" + help_minmax: "Mínimo, máximo" + help_rounding: "Arredondamento" + help_statistics: "Estatísticas (para deteção de anomalias)" + help_mean: "Média de todo o canal RPM" + help_stdev: "Desvio padrão" + help_min_max: "Valores mín/máx" + help_examples: "Exemplos" + help_ex_rate: "Variação de RPM por amostra" + help_ex_deviation: "Desvio % de AFR" + help_ex_zscore: "Z-score" + +# Normalization editor (src/ui/normalization_editor.rs) +normalization: + title: "Editor de Normalização de Campos" + field_mappings: "Mapeamentos de Nomes de Campos" + reset_defaults: "Repor Predefinições" + extend_builtin: "Estender Mapeamentos Incorporados" + extend_description: "Adicione os seus próprios nomes de origem que mapeiam para nomes de campos normalizados existentes." + source_name: "Nome de Origem:" + source_hint: "ex., MeuRPMPersonalizado" + maps_to: "Mapeia Para:" + select: "Selecionar..." + create_new: "Criar Novo Mapeamento" + create_description: "Defina nomes normalizados completamente novos para os seus canais personalizados." + custom_source_hint: "ex., Sensor_XYZ" + display_as: "Apresentar Como:" + custom_target_hint: "ex., Sensor Personalizado" + your_mappings: "Os Seus Mapeamentos Personalizados" + source: "Origem" + builtin_reference: "Referência de Mapeamentos Incorporados" + builtin_description: "Estes são os mapeamentos predefinidos. Pode estendê-los adicionando novos nomes de origem acima." + +# Scatter plot (src/ui/scatter_plot.rs) +scatter: + no_file_loaded: "Carregue um ficheiro de registo para usar gráficos de dispersão" + x_axis: "Eixo X:" + y_axis: "Eixo Y:" + z_axis: "Eixo Z:" + hits: "Ocorrências" + hits_label: "Ocorrências:" + remove: "X" + +# Histogram (src/ui/histogram.rs) +histogram: + no_file_loaded: "Carregue um ficheiro de registo para usar o histograma" + x_axis: "Eixo X:" + y_axis: "Eixo Y:" + z_axis: "Eixo Z:" + mode: "Modo:" + average_z: "Média Z" + hit_count: "Contagem de Ocorrências" + grid: "Grelha:" + +# Chart (src/ui/chart.rs) +chart: + select_channels: "Selecione canais para apresentar o gráfico" + +# Analysis panel (src/ui/analysis_panel.rs) +analysis: + window_title: "Ferramentas de Análise" + no_file_loaded: "Nenhum ficheiro de registo carregado" + load_file_help: "Carregue um ficheiro de registo para aceder às ferramentas de análise." + category_all: "Todos" + category_filters: "Filtros" + category_statistics: "Estatísticas" + category_afr: "AFR" + category_derived: "Derivados" + results_count: "Resultados (%{count})" + clear_all: "Limpar Tudo" + all_tools: "Todas as Ferramentas" + no_analyzers: "Nenhum analisador nesta categoria" + run_and_chart: "Executar e Gráfico" + run: "Executar" + add_chart: "+ Gráfico" + remove_all_tooltip: "Remover todos os resultados" + select_valid_channels: "Selecione primeiro canais válidos" + run_add_tooltip: "Executar análise e adicionar resultado ao gráfico" + run_tooltip: "Executar análise (adicionar ao gráfico depois)" + remove_result_tooltip: "Remover resultado" + add_to_chart_result: "Adicionar ao gráfico como canal" + +# Tool switcher (src/ui/tool_switcher.rs) +tools: + log_viewer: "Visualizador de Registos" + scatter_plots: "Gráficos de Dispersão" + histogram: "Histograma" + +# Activity bar (src/ui/activity_bar.rs) +activity: + files_tooltip: "Ficheiros" + channels_tooltip: "Canais" + tools_tooltip: "Ferramentas" + settings_tooltip: "Definições" diff --git a/i18n/ru.yaml b/i18n/ru.yaml new file mode 100644 index 0000000..73fd458 --- /dev/null +++ b/i18n/ru.yaml @@ -0,0 +1,335 @@ +# Russian translations for UltraLog + +# Menu bar (src/ui/menu.rs) +menu: + file: "Файл" + open_log_file: "Открыть лог-файл..." + close_tab: "Закрыть вкладку" + export: "Экспорт" + export_png: "Экспортировать в PNG..." + export_pdf: "Экспортировать в PDF..." + export_histogram_pdf: "Экспортировать гистограмму в PDF..." + view: "Вид" + tool_mode: "Режим инструмента" + log_viewer: "Просмотр логов" + scatter_plots: "Диаграммы рассеяния" + histogram: "Гистограмма" + side_panel: "Боковая панель" + files: "Файлы" + channels: "Каналы" + tools: "Инструменты" + settings: "Настройки" + help: "Справка" + documentation: "Документация" + report_issue: "Сообщить о проблеме" + support_development: "Поддержать разработку" + check_for_updates: "Проверить обновления" + checking_for_updates: "Проверка обновлений..." + version: "Версия %{version}" + +# Files panel (src/ui/files_panel.rs) +files: + loading: "Загрузка %{filename}..." + loaded_files: "Загруженные файлы (%{count})" + add_file: "Добавить файл" + select_file: "Выберите файл" + or: "или" + drop_file_here: "Перетащите файл сюда" + file_info: "%{ecu} • %{channels} кан. • %{points} точек" + +# Channels panel (src/ui/channels_panel.rs) +channels: + computed_channels: "Вычисляемые каналы" + computed_channels_tooltip: "Создание виртуальных каналов на основе математических формул" + search_hint: "Поиск каналов..." + selected_count: "Выбрано: %{selected} / %{max} | Всего: %{total}" + no_file_selected: "Файл не выбран" + load_file_to_view: "Загрузите файл для просмотра каналов" + with_data: "С данными (%{count})" + empty: "Пустые (%{count})" + +# Settings panel (src/ui/settings_panel.rs) +settings: + language: "Язык" + language_desc: "Выберите язык интерфейса" + display: "Отображение" + color_blind_mode: "Режим для дальтоников" + color_blind_desc: "Использовать доступную цветовую палитру (палитра Вонга)" + font_size: "Размер шрифта:" + cursor_tracking: "Отслеживание курсора" + cursor_tracking_desc: "Удерживать курсор в центре при прокрутке" + window: "Окно:" + field_names: "Имена полей" + field_normalization: "Нормализация полей" + field_normalization_desc: "Стандартизировать имена каналов для разных типов ECU" + custom_mappings: "%{count} пользовательских соответствий" + edit_custom_mappings: "Редактировать соответствия" + units: "Единицы измерения" + units_desc: "Выберите предпочтительные единицы измерения" + temperature: "Температура:" + pressure: "Давление:" + speed: "Скорость:" + distance: "Расстояние:" + fuel_economy: "Расход топлива:" + volume: "Объём:" + flow_rate: "Расход:" + acceleration: "Ускорение:" + updates: "Обновления" + check_on_startup: "Проверять при запуске" + auto_check_desc: "Автоматически проверять наличие новых версий" + check_for_updates: "Проверить обновления" + checking: "Проверка..." + current_version: "Текущая версия: %{version}" + update_available: "Доступно обновление: v%{version}" + view_details: "Подробнее" + +# Timeline (src/ui/timeline.rs) +timeline: + speed: "Скорость:" + time: "Время: %{time}" + record: "Запись %{current} из %{total}" + +# Formula editor (src/ui/formula_editor.rs) +formula: + new_computed_channel: "Новый вычисляемый канал" + edit_computed_channel: "Редактирование вычисляемого канала" + name: "Название:" + name_hint: "например, Дельта RPM" + formula: "Формула:" + formula_hint: "например, RPM - RPM[-1]" + insert: "Вставить:" + insert_tooltip: "Вставить %{op}" + prev_sample_tooltip: "Предыдущий отсчёт (смещение индекса)" + time_ago_tooltip: "Значение 0.1 секунды назад" + error: "Ошибка:" + formula_valid: "Формула корректна" + unit: "Единица:" + unit_hint: "например, RPM/с" + description: "Описание:" + description_hint: "Необязательное описание" + available_channels: "Доступные каналы" + no_channels_available: "Нет доступных каналов — сначала загрузите лог-файл" + click_to_insert: "Нажмите, чтобы вставить в формулу:" + preview: "Предпросмотр" + min: "Мин:" + avg: "Сред:" + max: "Макс:" + sample: "Отсчёт:" + cancel: "Отмена" + save: "Сохранить" + validate: "Проверить" + +# Update dialog (src/ui/update_dialog.rs) +update: + update_available: "Доступно обновление" + new_version_available: "Доступна новая версия!" + current_version: "Текущая версия:" + new_version: "Новая версия:" + download_size: "Размер загрузки: %{size} МБ" + release_notes: "Примечания к выпуску" + download_install: "Скачать и установить" + view_on_github: "Открыть на GitHub" + later: "Позже" + downloading: "Загрузка обновления..." + please_wait: "Пожалуйста, подождите..." + download_complete: "Загрузка завершена!" + install_instructions: "Нажмите «Установить», чтобы применить обновление." + windows_restart: "Приложение закроется и перезапустится автоматически." + macos_dmg: "Откроется DMG — перетащите UltraLog в папку «Программы»." + linux_restart: "Приложение закроется и перезапустится автоматически." + install_now: "Установить сейчас" + install_later: "Установить позже" + update_error: "Ошибка обновления" + close: "Закрыть" + update_saved: "Обновление сохранено во временную папку." + +# Toast messages (src/app.rs) +toast: + file_already_loaded: "Файл уже загружен" + file_loaded: "Файл успешно загружен" + max_channels: "Достигнут максимум в 10 каналов" + channel_already_selected: "Канал уже выбран" + channel_from_active_tab: "Канал должен быть из файла активной вкладки" + no_active_tab: "Нет активной вкладки" + library_saved: "Библиотека сохранена" + library_save_failed: "Не удалось сохранить библиотеку: %{error}" + channel_saved: "Канал сохранён в библиотеку" + up_to_date: "У вас установлена последняя версия" + update_check_failed: "Не удалось проверить обновления: %{error}" + update_downloaded: "Обновление успешно загружено" + download_failed: "Ошибка загрузки: %{error}" + export_png_success: "График экспортирован в PNG" + export_pdf_success: "График экспортирован в PDF" + export_failed: "Ошибка экспорта: %{error}" + histogram_exported_png: "Гистограмма экспортирована в PNG" + histogram_exported_pdf: "Гистограмма экспортирована в PDF" + scatter_exported_png: "Диаграмма рассеяния экспортирована в PNG" + scatter_exported_pdf: "Диаграмма рассеяния экспортирована в PDF" + failed_to_save: "Не удалось сохранить: %{error}" + failed_to_apply: "Не удалось применить: %{error}" + evaluation_failed: "Ошибка вычисления: %{error}" + template_duplicated: "Шаблон скопирован" + applied_template: "Применён шаблон «%{name}»" + added_to_chart: "«%{name}» добавлен на график" + channel_already_on_chart: "Канал уже на графике" + max_channels_reached: "Достигнут максимум в 10 каналов" + analysis_complete: "Анализ завершён: %{name}" + analysis_failed: "Ошибка анализа: %{error}" + analyzer_not_found: "Анализатор не найден: %{id}" + no_file_selected: "Файл не выбран" + file_not_found: "Файл не найден" + +# Common/shared strings +common: + loading: "Загрузка..." + error: "Ошибка" + success: "Успешно" + warning: "Предупреждение" + info: "Информация" + ok: "ОК" + yes: "Да" + no: "Нет" + remove: "Удалить" + add: "Добавить" + edit: "Редактировать" + delete: "Удалить" + duplicate: "Дублировать" + apply: "Применить" + close: "Закрыть" + reset: "Сбросить" + +# Computed channels manager (src/ui/computed_channels_manager.rs) +computed: + title: "Вычисляемые каналы" + show_help: "Показать справку по синтаксису формул" + new: "+ Новый" + description: "Создание виртуальных каналов на основе математических формул." + quick_create: "Быстрое создание:" + rate_of_change: "Скорость изменения" + rate_of_change_hint: "Создать: Channel - Channel[-1]" + rate_of_change_desc: "Скорость изменения за отсчёт" + moving_avg: "Скользящее среднее" + moving_avg_hint: "Создать: скользящее среднее по 3 отсчётам" + moving_avg_desc: "Скользящее среднее по 3 отсчётам для сглаживания" + deviation: "% отклонения" + deviation_hint: "Создать: процентное отклонение от целевого значения" + deviation_desc: "Процентное отклонение от целевого значения" + your_library: "Ваша библиотека (%{count})" + search: "Поиск..." + no_channels: "Вычисляемых каналов пока нет" + get_started: "Используйте быстрое создание или нажмите «+ Новый»" + copy: "копия" + builtin_template: "Встроенный шаблон" + applied_to_file: "Применено к текущему файлу (%{count})" + apply_templates_hint: "Применить шаблоны из библиотеки выше" + add_chart: "+ График" + add_to_chart: "Добавить на график" + load_file_hint: "Загрузите лог-файл, чтобы применить вычисляемые каналы" + help_title: "Справка по формулам" + help_channel_refs: "Ссылки на каналы" + help_current_value: "Текущее значение канала RPM" + help_quoted_channel: "Каналы с пробелами (используйте кавычки)" + help_prev_sample: "Предыдущий отсчёт (смещение индекса)" + help_samples_ahead: "2 отсчёта вперёд" + help_time_offset: "Значение 100 мс назад (смещение по времени)" + help_operators: "Операторы" + help_basic_math: "Основная арифметика" + help_power: "Степень (например, RPM^2)" + help_grouping: "Группировка" + help_functions: "Функции" + help_trig: "Тригонометрия" + help_sqrt_abs: "Квадратный корень, модуль" + help_log: "Логарифмы, экспонента" + help_minmax: "Минимум, максимум" + help_rounding: "Округление" + help_statistics: "Статистика (для обнаружения аномалий)" + help_mean: "Среднее значение всего канала RPM" + help_stdev: "Стандартное отклонение" + help_min_max: "Минимальные/максимальные значения" + help_examples: "Примеры" + help_ex_rate: "Изменение RPM за отсчёт" + help_ex_deviation: "Отклонение AFR в %" + help_ex_zscore: "Z-score" + +# Normalization editor (src/ui/normalization_editor.rs) +normalization: + title: "Редактор нормализации полей" + field_mappings: "Соответствия имён полей" + reset_defaults: "Сбросить по умолчанию" + extend_builtin: "Расширить встроенные соответствия" + extend_description: "Добавьте собственные исходные имена, соответствующие существующим нормализованным именам полей." + source_name: "Исходное имя:" + source_hint: "например, MyCustomRPM" + maps_to: "Соответствует:" + select: "Выбрать..." + create_new: "Создать новое соответствие" + create_description: "Определите полностью новые нормализованные имена для ваших каналов." + custom_source_hint: "например, Sensor_XYZ" + display_as: "Отображать как:" + custom_target_hint: "например, Пользовательский датчик" + your_mappings: "Ваши соответствия" + source: "Исходное" + builtin_reference: "Справочник встроенных соответствий" + builtin_description: "Это соответствия по умолчанию. Вы можете расширить их, добавив новые исходные имена выше." + +# Scatter plot (src/ui/scatter_plot.rs) +scatter: + no_file_loaded: "Загрузите лог-файл для использования диаграмм рассеяния" + x_axis: "Ось X:" + y_axis: "Ось Y:" + z_axis: "Ось Z:" + hits: "Попадания" + hits_label: "Попаданий:" + remove: "X" + +# Histogram (src/ui/histogram.rs) +histogram: + no_file_loaded: "Загрузите лог-файл для использования гистограммы" + x_axis: "Ось X:" + y_axis: "Ось Y:" + z_axis: "Ось Z:" + mode: "Режим:" + average_z: "Среднее Z" + hit_count: "Количество попаданий" + grid: "Сетка:" + +# Chart (src/ui/chart.rs) +chart: + select_channels: "Выберите каналы для отображения графика" + +# Analysis panel (src/ui/analysis_panel.rs) +analysis: + window_title: "Инструменты анализа" + no_file_loaded: "Лог-файл не загружен" + load_file_help: "Загрузите лог-файл для доступа к инструментам анализа." + category_all: "Все" + category_filters: "Фильтры" + category_statistics: "Статистика" + category_afr: "AFR" + category_derived: "Производные" + results_count: "Результаты (%{count})" + clear_all: "Очистить всё" + all_tools: "Все инструменты" + no_analyzers: "В этой категории нет анализаторов" + run_and_chart: "Запустить и добавить" + run: "Запустить" + add_chart: "+ График" + remove_all_tooltip: "Удалить все результаты" + select_valid_channels: "Сначала выберите допустимые каналы" + run_add_tooltip: "Запустить анализ и добавить результат на график" + run_tooltip: "Запустить анализ (добавить на график позже)" + remove_result_tooltip: "Удалить результат" + add_to_chart_result: "Добавить на график как канал" + +# Tool switcher (src/ui/tool_switcher.rs) +tools: + log_viewer: "Просмотр логов" + scatter_plots: "Диаграммы рассеяния" + histogram: "Гистограмма" + +# Activity bar (src/ui/activity_bar.rs) +activity: + files_tooltip: "Файлы" + channels_tooltip: "Каналы" + tools_tooltip: "Инструменты" + settings_tooltip: "Настройки" diff --git a/i18n/ur.yaml b/i18n/ur.yaml new file mode 100644 index 0000000..1445a21 --- /dev/null +++ b/i18n/ur.yaml @@ -0,0 +1,336 @@ +# English translations for UltraLog +# اردو ترجمہ برائے UltraLog + +# Menu bar (src/ui/menu.rs) +menu: + file: "فائل" + open_log_file: "لاگ فائل کھولیں..." + close_tab: "ٹیب بند کریں" + export: "برآمد" + export_png: "PNG کے طور پر برآمد کریں..." + export_pdf: "PDF کے طور پر برآمد کریں..." + export_histogram_pdf: "ہسٹوگرام PDF کے طور پر برآمد کریں..." + view: "منظر" + tool_mode: "ٹول موڈ" + log_viewer: "لاگ ویور" + scatter_plots: "سکیٹر پلاٹس" + histogram: "ہسٹوگرام" + side_panel: "سائیڈ پینل" + files: "فائلیں" + channels: "چینلز" + tools: "ٹولز" + settings: "ترتیبات" + help: "مدد" + documentation: "دستاویزات" + report_issue: "مسئلہ رپورٹ کریں" + support_development: "ترقی میں مدد کریں" + check_for_updates: "اپڈیٹس کی جانچ کریں" + checking_for_updates: "اپڈیٹس کی جانچ ہو رہی ہے..." + version: "ورژن %{version}" + +# Files panel (src/ui/files_panel.rs) +files: + loading: "%{filename} لوڈ ہو رہی ہے..." + loaded_files: "لوڈ شدہ فائلیں (%{count})" + add_file: "فائل شامل کریں" + select_file: "فائل منتخب کریں" + or: "یا" + drop_file_here: "فائل یہاں چھوڑیں" + file_info: "%{ecu} • %{channels} چینلز • %{points} پوائنٹس" + +# Channels panel (src/ui/channels_panel.rs) +channels: + computed_channels: "کمپیوٹڈ چینلز" + computed_channels_tooltip: "ریاضیاتی فارمولوں سے ورچوئل چینلز بنائیں" + search_hint: "چینلز تلاش کریں..." + selected_count: "منتخب: %{selected} / %{max} | کل: %{total}" + no_file_selected: "کوئی فائل منتخب نہیں" + load_file_to_view: "چینلز دیکھنے کے لیے فائل لوڈ کریں" + with_data: "ڈیٹا والے (%{count})" + empty: "خالی (%{count})" + +# Settings panel (src/ui/settings_panel.rs) +settings: + language: "زبان" + language_desc: "ڈسپلے زبان منتخب کریں" + display: "ڈسپلے" + color_blind_mode: "کلر بلائنڈ موڈ" + color_blind_desc: "قابل رسائی رنگ پیلٹ استعمال کریں (Wong's پیلٹ)" + font_size: "فونٹ سائز:" + cursor_tracking: "کرسر ٹریکنگ" + cursor_tracking_desc: "سکربنگ کے دوران کرسر کو مرکز میں رکھیں" + window: "ونڈو:" + field_names: "فیلڈ کے نام" + field_normalization: "فیلڈ نارملائزیشن" + field_normalization_desc: "ECU اقسام میں چینل ناموں کو معیاری بنائیں" + custom_mappings: "%{count} اپنی مرضی کی میپنگز" + edit_custom_mappings: "اپنی مرضی کی میپنگز میں ترمیم کریں" + units: "اکائیاں" + units_desc: "ڈسپلے کے لیے پسندیدہ اکائیاں منتخب کریں" + temperature: "درجہ حرارت:" + pressure: "دباؤ:" + speed: "رفتار:" + distance: "فاصلہ:" + fuel_economy: "ایندھن کی بچت:" + volume: "حجم:" + flow_rate: "بہاؤ کی شرح:" + acceleration: "ایکسلریشن:" + updates: "اپڈیٹس" + check_on_startup: "شروع میں جانچ کریں" + auto_check_desc: "خودکار طور پر نئے ورژن کی جانچ کریں" + check_for_updates: "اپڈیٹس کی جانچ کریں" + checking: "جانچ ہو رہی ہے..." + current_version: "موجودہ ورژن: %{version}" + update_available: "اپڈیٹ دستیاب: v%{version}" + view_details: "تفصیلات دیکھیں" + +# Timeline (src/ui/timeline.rs) +timeline: + speed: "رفتار:" + time: "وقت: %{time}" + record: "ریکارڈ %{current} از %{total}" + +# Formula editor (src/ui/formula_editor.rs) +formula: + new_computed_channel: "نیا کمپیوٹڈ چینل" + edit_computed_channel: "کمپیوٹڈ چینل میں ترمیم کریں" + name: "نام:" + name_hint: "مثال: RPM Delta" + formula: "فارمولا:" + formula_hint: "مثال: RPM - RPM[-1]" + insert: "داخل کریں:" + insert_tooltip: "%{op} داخل کریں" + prev_sample_tooltip: "پچھلا نمونہ (انڈیکس آفسیٹ)" + time_ago_tooltip: "0.1 سیکنڈ پہلے کی قیمت" + error: "خامی:" + formula_valid: "فارمولا درست ہے" + unit: "اکائی:" + unit_hint: "مثال: RPM/s" + description: "تفصیل:" + description_hint: "اختیاری تفصیل" + available_channels: "دستیاب چینلز" + no_channels_available: "کوئی چینل دستیاب نہیں - پہلے لاگ فائل لوڈ کریں" + click_to_insert: "فارمولے میں داخل کرنے کے لیے کلک کریں:" + preview: "پیش نظارہ" + min: "کم از کم:" + avg: "اوسط:" + max: "زیادہ سے زیادہ:" + sample: "نمونہ:" + cancel: "منسوخ" + save: "محفوظ کریں" + validate: "تصدیق کریں" + +# Update dialog (src/ui/update_dialog.rs) +update: + update_available: "اپڈیٹ دستیاب" + new_version_available: "نیا ورژن دستیاب ہے!" + current_version: "موجودہ ورژن:" + new_version: "نیا ورژن:" + download_size: "ڈاؤن لوڈ سائز: %{size} MB" + release_notes: "ریلیز نوٹس" + download_install: "ڈاؤن لوڈ اور انسٹال کریں" + view_on_github: "GitHub پر دیکھیں" + later: "بعد میں" + downloading: "اپڈیٹ ڈاؤن لوڈ ہو رہی ہے..." + please_wait: "براہ کرم انتظار کریں..." + download_complete: "ڈاؤن لوڈ مکمل!" + install_instructions: "اپڈیٹ لاگو کرنے کے لیے انسٹال پر کلک کریں۔" + windows_restart: "ایپلیکیشن بند ہو کر خودکار طور پر دوبارہ شروع ہوگی۔" + macos_dmg: "DMG کھلے گا - UltraLog کو Applications میں ڈریگ کریں۔" + linux_restart: "ایپلیکیشن بند ہو کر خودکار طور پر دوبارہ شروع ہوگی۔" + install_now: "ابھی انسٹال کریں" + install_later: "بعد میں انسٹال کریں" + update_error: "اپڈیٹ میں خامی" + close: "بند کریں" + update_saved: "اپڈیٹ آپ کے عارضی فولڈر میں محفوظ ہوگئی۔" + +# Toast messages (src/app.rs) +toast: + file_already_loaded: "فائل پہلے سے لوڈ ہے" + file_loaded: "فائل کامیابی سے لوڈ ہوگئی" + max_channels: "زیادہ سے زیادہ 10 چینلز تک پہنچ گئے" + channel_already_selected: "چینل پہلے سے منتخب ہے" + channel_from_active_tab: "چینل فعال ٹیب کی فائل سے ہونا چاہیے" + no_active_tab: "کوئی فعال ٹیب نہیں" + library_saved: "لائبریری محفوظ ہوگئی" + library_save_failed: "لائبریری محفوظ کرنے میں ناکامی: %{error}" + channel_saved: "چینل لائبریری میں محفوظ ہوگیا" + up_to_date: "آپ تازہ ترین ورژن چلا رہے ہیں" + update_check_failed: "اپڈیٹ کی جانچ ناکام: %{error}" + update_downloaded: "اپڈیٹ کامیابی سے ڈاؤن لوڈ ہوگئی" + download_failed: "ڈاؤن لوڈ ناکام: %{error}" + export_png_success: "چارٹ PNG کے طور پر برآمد ہوگیا" + export_pdf_success: "چارٹ PDF کے طور پر برآمد ہوگیا" + export_failed: "برآمد ناکام: %{error}" + histogram_exported_png: "ہسٹوگرام PNG کے طور پر برآمد ہوگیا" + histogram_exported_pdf: "ہسٹوگرام PDF کے طور پر برآمد ہوگیا" + scatter_exported_png: "سکیٹر پلاٹ PNG کے طور پر برآمد ہوگیا" + scatter_exported_pdf: "سکیٹر پلاٹ PDF کے طور پر برآمد ہوگیا" + failed_to_save: "محفوظ کرنے میں ناکامی: %{error}" + failed_to_apply: "لاگو کرنے میں ناکامی: %{error}" + evaluation_failed: "تشخیص ناکام: %{error}" + template_duplicated: "ٹیمپلیٹ نقل ہوگیا" + applied_template: "'%{name}' لاگو ہوگیا" + added_to_chart: "'%{name}' چارٹ میں شامل ہوگیا" + channel_already_on_chart: "چینل پہلے سے چارٹ پر ہے" + max_channels_reached: "زیادہ سے زیادہ 10 چینلز تک پہنچ گئے" + analysis_complete: "تجزیہ مکمل: %{name}" + analysis_failed: "تجزیہ ناکام: %{error}" + analyzer_not_found: "تجزیہ کار نہیں ملا: %{id}" + no_file_selected: "کوئی فائل منتخب نہیں" + file_not_found: "فائل نہیں ملی" + +# Common/shared strings +common: + loading: "لوڈ ہو رہا ہے..." + error: "خامی" + success: "کامیابی" + warning: "انتباہ" + info: "معلومات" + ok: "ٹھیک ہے" + yes: "ہاں" + no: "نہیں" + remove: "ہٹائیں" + add: "شامل کریں" + edit: "ترمیم" + delete: "حذف کریں" + duplicate: "نقل بنائیں" + apply: "لاگو کریں" + close: "بند کریں" + reset: "ری سیٹ" + +# Computed channels manager (src/ui/computed_channels_manager.rs) +computed: + title: "کمپیوٹڈ چینلز" + show_help: "فارمولا سنٹیکس مدد دکھائیں" + new: "+ نیا" + description: "ریاضیاتی فارمولوں سے ورچوئل چینلز بنائیں۔" + quick_create: "فوری تخلیق:" + rate_of_change: "تبدیلی کی شرح" + rate_of_change_hint: "بنائیں: Channel - Channel[-1]" + rate_of_change_desc: "فی نمونہ تبدیلی کی شرح" + moving_avg: "موونگ اوسط" + moving_avg_hint: "بنائیں: 3 نمونوں کی موونگ اوسط" + moving_avg_desc: "ہموار کرنے کے لیے 3 نمونوں کی موونگ اوسط" + deviation: "% انحراف" + deviation_hint: "بنائیں: ہدف سے فیصد انحراف" + deviation_desc: "ہدف قیمت سے فیصد انحراف" + your_library: "آپ کی لائبریری (%{count})" + search: "تلاش..." + no_channels: "ابھی تک کوئی کمپیوٹڈ چینلز نہیں" + get_started: "شروع کرنے کے لیے فوری تخلیق استعمال کریں یا '+ نیا' پر کلک کریں" + copy: "نقل" + builtin_template: "بلٹ ان ٹیمپلیٹ" + applied_to_file: "موجودہ فائل پر لاگو (%{count})" + apply_templates_hint: "اوپر کی لائبریری سے ٹیمپلیٹس لاگو کریں" + add_chart: "+ چارٹ" + add_to_chart: "چارٹ میں شامل کریں" + load_file_hint: "کمپیوٹڈ چینلز لاگو کرنے کے لیے لاگ فائل لوڈ کریں" + help_title: "فارمولا مدد" + help_channel_refs: "چینل حوالے" + help_current_value: "RPM چینل کی موجودہ قیمت" + help_quoted_channel: "خالی جگہوں والے چینلز (کوٹیشن استعمال کریں)" + help_prev_sample: "پچھلا نمونہ (انڈیکس آفسیٹ)" + help_samples_ahead: "2 نمونے آگے" + help_time_offset: "100ms پہلے کی قیمت (وقت آفسیٹ)" + help_operators: "آپریٹرز" + help_basic_math: "بنیادی ریاضی" + help_power: "پاور (مثال: RPM^2)" + help_grouping: "گروپنگ" + help_functions: "فنکشنز" + help_trig: "مثلثیات" + help_sqrt_abs: "اسکوائر روٹ، مطلق قدر" + help_log: "لوگارتھم، ایکسپونینشل" + help_minmax: "کم از کم، زیادہ سے زیادہ" + help_rounding: "راؤنڈنگ" + help_statistics: "شماریات (غیر معمولی پن کی نشاندہی کے لیے)" + help_mean: "پورے RPM چینل کا اوسط" + help_stdev: "معیاری انحراف" + help_min_max: "کم سے کم/زیادہ سے زیادہ قدریں" + help_examples: "مثالیں" + help_ex_rate: "فی نمونہ RPM تبدیلی" + help_ex_deviation: "AFR % انحراف" + help_ex_zscore: "Z-score" + +# Normalization editor (src/ui/normalization_editor.rs) +normalization: + title: "فیلڈ نارملائزیشن ایڈیٹر" + field_mappings: "فیلڈ نام میپنگز" + reset_defaults: "پہلے سے طے شدہ پر ری سیٹ کریں" + extend_builtin: "بلٹ ان میپنگز میں توسیع کریں" + extend_description: "اپنے سورس نام شامل کریں جو موجودہ نارملائزڈ فیلڈ ناموں سے میپ ہوں۔" + source_name: "سورس نام:" + source_hint: "مثال: MyCustomRPM" + maps_to: "میپ ہوتا ہے:" + select: "منتخب کریں..." + create_new: "نئی میپنگ بنائیں" + create_description: "اپنے کسٹم چینلز کے لیے بالکل نئے نارملائزڈ نام بنائیں۔" + custom_source_hint: "مثال: Sensor_XYZ" + display_as: "ظاہر ہو بطور:" + custom_target_hint: "مثال: کسٹم سینسر" + your_mappings: "آپ کی کسٹم میپنگز" + source: "سورس" + builtin_reference: "بلٹ ان میپنگز حوالہ" + builtin_description: "یہ پہلے سے طے شدہ میپنگز ہیں۔ آپ اوپر نئے سورس نام شامل کر کے ان میں توسیع کر سکتے ہیں۔" + +# Scatter plot (src/ui/scatter_plot.rs) +scatter: + no_file_loaded: "سکیٹر پلاٹس استعمال کرنے کے لیے لاگ فائل لوڈ کریں" + x_axis: "X محور:" + y_axis: "Y محور:" + z_axis: "Z محور:" + hits: "ہٹس" + hits_label: "ہٹس:" + remove: "X" + +# Histogram (src/ui/histogram.rs) +histogram: + no_file_loaded: "ہسٹوگرام استعمال کرنے کے لیے لاگ فائل لوڈ کریں" + x_axis: "X محور:" + y_axis: "Y محور:" + z_axis: "Z محور:" + mode: "موڈ:" + average_z: "اوسط Z" + hit_count: "ہٹ کاؤنٹ" + grid: "گرڈ:" + +# Chart (src/ui/chart.rs) +chart: + select_channels: "چارٹ دکھانے کے لیے چینلز منتخب کریں" + +# Analysis panel (src/ui/analysis_panel.rs) +analysis: + window_title: "تجزیہ ٹولز" + no_file_loaded: "کوئی لاگ فائل لوڈ نہیں" + load_file_help: "تجزیہ ٹولز تک رسائی کے لیے لاگ فائل لوڈ کریں۔" + category_all: "سب" + category_filters: "فلٹرز" + category_statistics: "شماریات" + category_afr: "AFR" + category_derived: "مشتق" + results_count: "نتائج (%{count})" + clear_all: "سب صاف کریں" + all_tools: "تمام ٹولز" + no_analyzers: "اس زمرے میں کوئی تجزیہ کار نہیں" + run_and_chart: "چلائیں اور چارٹ کریں" + run: "چلائیں" + add_chart: "+ چارٹ" + remove_all_tooltip: "تمام نتائج ہٹائیں" + select_valid_channels: "پہلے درست چینلز منتخب کریں" + run_add_tooltip: "تجزیہ چلائیں اور نتیجہ چارٹ میں شامل کریں" + run_tooltip: "تجزیہ چلائیں (بعد میں چارٹ میں شامل کریں)" + remove_result_tooltip: "نتیجہ ہٹائیں" + add_to_chart_result: "چینل کے طور پر چارٹ میں شامل کریں" + +# Tool switcher (src/ui/tool_switcher.rs) +tools: + log_viewer: "لاگ ویور" + scatter_plots: "سکیٹر پلاٹس" + histogram: "ہسٹوگرام" + +# Activity bar (src/ui/activity_bar.rs) +activity: + files_tooltip: "فائلیں" + channels_tooltip: "چینلز" + tools_tooltip: "ٹولز" + settings_tooltip: "ترتیبات" diff --git a/i18n/zh-CN.yaml b/i18n/zh-CN.yaml new file mode 100644 index 0000000..7bb2a5c --- /dev/null +++ b/i18n/zh-CN.yaml @@ -0,0 +1,335 @@ +# 简体中文翻译 - UltraLog + +# 菜单栏 (src/ui/menu.rs) +menu: + file: "文件" + open_log_file: "打开日志文件..." + close_tab: "关闭标签页" + export: "导出" + export_png: "导出为 PNG..." + export_pdf: "导出为 PDF..." + export_histogram_pdf: "导出直方图为 PDF..." + view: "视图" + tool_mode: "工具模式" + log_viewer: "日志查看器" + scatter_plots: "散点图" + histogram: "直方图" + side_panel: "侧边栏" + files: "文件" + channels: "通道" + tools: "工具" + settings: "设置" + help: "帮助" + documentation: "文档" + report_issue: "报告问题" + support_development: "支持开发" + check_for_updates: "检查更新" + checking_for_updates: "正在检查更新..." + version: "版本 %{version}" + +# 文件面板 (src/ui/files_panel.rs) +files: + loading: "正在加载 %{filename}..." + loaded_files: "已加载文件 (%{count})" + add_file: "添加文件" + select_file: "选择文件" + or: "或" + drop_file_here: "拖放文件到此处" + file_info: "%{ecu} • %{channels} 通道 • %{points} 数据点" + +# 通道面板 (src/ui/channels_panel.rs) +channels: + computed_channels: "计算通道" + computed_channels_tooltip: "使用数学公式创建虚拟通道" + search_hint: "搜索通道..." + selected_count: "已选: %{selected} / %{max} | 总计: %{total}" + no_file_selected: "未选择文件" + load_file_to_view: "加载文件以查看通道" + with_data: "有数据 (%{count})" + empty: "无数据 (%{count})" + +# 设置面板 (src/ui/settings_panel.rs) +settings: + language: "语言" + language_desc: "选择显示语言" + display: "显示" + color_blind_mode: "色盲模式" + color_blind_desc: "使用无障碍色彩方案 (Wong 配色)" + font_size: "字体大小:" + cursor_tracking: "光标跟踪" + cursor_tracking_desc: "拖动时保持光标居中" + window: "窗口:" + field_names: "字段名称" + field_normalization: "字段标准化" + field_normalization_desc: "统一不同 ECU 类型的通道名称" + custom_mappings: "%{count} 个自定义映射" + edit_custom_mappings: "编辑自定义映射" + units: "单位" + units_desc: "选择显示单位偏好" + temperature: "温度:" + pressure: "压力:" + speed: "速度:" + distance: "距离:" + fuel_economy: "燃油经济性:" + volume: "容积:" + flow_rate: "流量:" + acceleration: "加速度:" + updates: "更新" + check_on_startup: "启动时检查" + auto_check_desc: "自动检查新版本" + check_for_updates: "检查更新" + checking: "正在检查..." + current_version: "当前版本: %{version}" + update_available: "可用更新: v%{version}" + view_details: "查看详情" + +# 时间轴 (src/ui/timeline.rs) +timeline: + speed: "速度:" + time: "时间: %{time}" + record: "记录 %{current} / %{total}" + +# 公式编辑器 (src/ui/formula_editor.rs) +formula: + new_computed_channel: "新建计算通道" + edit_computed_channel: "编辑计算通道" + name: "名称:" + name_hint: "例如: RPM 变化率" + formula: "公式:" + formula_hint: "例如: RPM - RPM[-1]" + insert: "插入:" + insert_tooltip: "插入 %{op}" + prev_sample_tooltip: "上一个采样值 (索引偏移)" + time_ago_tooltip: "0.1 秒前的值" + error: "错误:" + formula_valid: "公式有效" + unit: "单位:" + unit_hint: "例如: RPM/s" + description: "描述:" + description_hint: "可选描述" + available_channels: "可用通道" + no_channels_available: "没有可用通道 - 请先加载日志文件" + click_to_insert: "点击插入到公式:" + preview: "预览" + min: "最小值:" + avg: "平均值:" + max: "最大值:" + sample: "采样:" + cancel: "取消" + save: "保存" + validate: "验证" + +# 更新对话框 (src/ui/update_dialog.rs) +update: + update_available: "有可用更新" + new_version_available: "发现新版本!" + current_version: "当前版本:" + new_version: "新版本:" + download_size: "下载大小: %{size} MB" + release_notes: "更新说明" + download_install: "下载并安装" + view_on_github: "在 GitHub 查看" + later: "稍后" + downloading: "正在下载更新..." + please_wait: "请稍候..." + download_complete: "下载完成!" + install_instructions: "点击安装以应用更新。" + windows_restart: "应用程序将自动关闭并重启。" + macos_dmg: "DMG 将打开 - 请将 UltraLog 拖到应用程序文件夹。" + linux_restart: "应用程序将自动关闭并重启。" + install_now: "立即安装" + install_later: "稍后安装" + update_error: "更新错误" + close: "关闭" + update_saved: "更新已保存到临时文件夹。" + +# 提示消息 (src/app.rs) +toast: + file_already_loaded: "文件已加载" + file_loaded: "文件加载成功" + max_channels: "已达到最大通道数 10" + channel_already_selected: "通道已被选中" + channel_from_active_tab: "通道必须来自当前活动标签页的文件" + no_active_tab: "没有活动标签页" + library_saved: "库已保存" + library_save_failed: "保存库失败: %{error}" + channel_saved: "通道已保存到库" + up_to_date: "您正在使用最新版本" + update_check_failed: "检查更新失败: %{error}" + update_downloaded: "更新下载成功" + download_failed: "下载失败: %{error}" + export_png_success: "图表已导出为 PNG" + export_pdf_success: "图表已导出为 PDF" + export_failed: "导出失败: %{error}" + histogram_exported_png: "直方图已导出为 PNG" + histogram_exported_pdf: "直方图已导出为 PDF" + scatter_exported_png: "散点图已导出为 PNG" + scatter_exported_pdf: "散点图已导出为 PDF" + failed_to_save: "保存失败: %{error}" + failed_to_apply: "应用失败: %{error}" + evaluation_failed: "计算失败: %{error}" + template_duplicated: "模板已复制" + applied_template: "已应用 '%{name}'" + added_to_chart: "'%{name}' 已添加到图表" + channel_already_on_chart: "通道已在图表中" + max_channels_reached: "已达到最大通道数 10" + analysis_complete: "分析完成: %{name}" + analysis_failed: "分析失败: %{error}" + analyzer_not_found: "未找到分析器: %{id}" + no_file_selected: "未选择文件" + file_not_found: "未找到文件" + +# 通用/共享字符串 +common: + loading: "加载中..." + error: "错误" + success: "成功" + warning: "警告" + info: "信息" + ok: "确定" + yes: "是" + no: "否" + remove: "移除" + add: "添加" + edit: "编辑" + delete: "删除" + duplicate: "复制" + apply: "应用" + close: "关闭" + reset: "重置" + +# 计算通道管理器 (src/ui/computed_channels_manager.rs) +computed: + title: "计算通道" + show_help: "显示公式语法帮助" + new: "+ 新建" + description: "使用数学公式创建虚拟通道。" + quick_create: "快速创建:" + rate_of_change: "变化率" + rate_of_change_hint: "创建: 通道 - 通道[-1]" + rate_of_change_desc: "每采样的变化率" + moving_avg: "移动平均" + moving_avg_hint: "创建: 3 点移动平均" + moving_avg_desc: "用于平滑的 3 点移动平均" + deviation: "% 偏差" + deviation_hint: "创建: 相对于目标值的百分比偏差" + deviation_desc: "相对于目标值的百分比偏差" + your_library: "我的库 (%{count})" + search: "🔍 搜索..." + no_channels: "暂无计算通道" + get_started: "使用快速创建或点击 '+ 新建' 开始" + copy: "复制" + builtin_template: "内置模板" + applied_to_file: "已应用到当前文件 (%{count})" + apply_templates_hint: "从上方库中应用模板" + add_chart: "+ 图表" + add_to_chart: "添加到图表" + load_file_hint: "加载日志文件以应用计算通道" + help_title: "公式帮助" + help_channel_refs: "通道引用" + help_current_value: "RPM 通道的当前值" + help_quoted_channel: "带空格的通道 (使用引号)" + help_prev_sample: "上一个采样值 (索引偏移)" + help_samples_ahead: "向前 2 个采样" + help_time_offset: "100 毫秒前的值 (时间偏移)" + help_operators: "运算符" + help_basic_math: "基本运算" + help_power: "幂运算 (例如 RPM^2)" + help_grouping: "分组" + help_functions: "函数" + help_trig: "三角函数" + help_sqrt_abs: "平方根, 绝对值" + help_log: "对数, 指数" + help_minmax: "最小值, 最大值" + help_rounding: "取整" + help_statistics: "统计 (用于异常检测)" + help_mean: "整个 RPM 通道的平均值" + help_stdev: "标准差" + help_min_max: "最小/最大值" + help_examples: "示例" + help_ex_rate: "每采样的 RPM 变化" + help_ex_deviation: "AFR 百分比偏差" + help_ex_zscore: "Z 分数" + +# 字段标准化编辑器 (src/ui/normalization_editor.rs) +normalization: + title: "字段标准化编辑器" + field_mappings: "字段名称映射" + reset_defaults: "恢复默认" + extend_builtin: "扩展内置映射" + extend_description: "添加您自己的源名称,映射到现有的标准化字段名称。" + source_name: "源名称:" + source_hint: "例如: MyCustomRPM" + maps_to: "映射到:" + select: "选择..." + create_new: "创建新映射" + create_description: "为您的自定义通道定义全新的标准化名称。" + custom_source_hint: "例如: Sensor_XYZ" + display_as: "显示为:" + custom_target_hint: "例如: 自定义传感器" + your_mappings: "您的自定义映射" + source: "源" + builtin_reference: "内置映射参考" + builtin_description: "这些是默认映射。您可以通过上方添加新的源名称来扩展它们。" + +# 散点图 (src/ui/scatter_plot.rs) +scatter: + no_file_loaded: "加载日志文件以使用散点图" + x_axis: "X 轴:" + y_axis: "Y 轴:" + z_axis: "Z 轴:" + hits: "命中" + hits_label: "命中:" + remove: "X" + +# 直方图 (src/ui/histogram.rs) +histogram: + no_file_loaded: "加载日志文件以使用直方图" + x_axis: "X 轴:" + y_axis: "Y 轴:" + z_axis: "Z 轴:" + mode: "模式:" + average_z: "Z 轴平均值" + hit_count: "命中次数" + grid: "网格:" + +# 图表 (src/ui/chart.rs) +chart: + select_channels: "选择通道以显示图表" + +# 分析面板 (src/ui/analysis_panel.rs) +analysis: + window_title: "分析工具" + no_file_loaded: "未加载日志文件" + load_file_help: "加载日志文件以访问分析工具。" + category_all: "全部" + category_filters: "滤波器" + category_statistics: "统计" + category_afr: "AFR" + category_derived: "衍生" + results_count: "结果 (%{count})" + clear_all: "全部清除" + all_tools: "所有工具" + no_analyzers: "此类别中没有分析器" + run_and_chart: "运行并绘图" + run: "运行" + add_chart: "+ 图表" + remove_all_tooltip: "移除所有结果" + select_valid_channels: "请先选择有效通道" + run_add_tooltip: "运行分析并将结果添加到图表" + run_tooltip: "运行分析 (稍后添加到图表)" + remove_result_tooltip: "移除结果" + add_to_chart_result: "作为通道添加到图表" + +# 工具切换器 (src/ui/tool_switcher.rs) +tools: + log_viewer: "日志查看器" + scatter_plots: "散点图" + histogram: "直方图" + +# 活动栏 (src/ui/activity_bar.rs) +activity: + files_tooltip: "文件" + channels_tooltip: "通道" + tools_tooltip: "工具" + settings_tooltip: "设置" diff --git a/src/app.rs b/src/app.rs index cfdee2f..87dc88c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -14,9 +14,11 @@ use std::thread; use crate::analysis::{AnalysisResult, AnalyzerRegistry}; use crate::analytics; use crate::computed::{ComputedChannel, ComputedChannelLibrary, FormulaEditorState}; +use crate::i18n::Language; use crate::parsers::{ Aim, EcuMaster, EcuType, Emerald, Haltech, Link, Parseable, RomRaider, Speeduino, }; +use crate::settings::UserSettings; use crate::state::{ ActivePanel, ActiveTool, CacheKey, FontScale, LoadResult, LoadedFile, LoadingState, ScatterPlotConfig, ScatterPlotState, SelectedChannel, Tab, ToastType, CHART_COLORS, @@ -139,6 +141,11 @@ pub struct UltraLogApp { pub(crate) show_analysis_panel: bool, /// Selected category in analysis panel (None = show all) pub(crate) analysis_selected_category: Option, + // === Internationalization === + /// User settings (persisted to disk) + pub(crate) user_settings: UserSettings, + /// Current language selection + pub(crate) language: Language, } impl Default for UltraLogApp { @@ -192,6 +199,8 @@ impl Default for UltraLogApp { analysis_results: HashMap::new(), show_analysis_panel: false, analysis_selected_category: None, + user_settings: UserSettings::default(), + language: Language::default(), } } } @@ -231,7 +240,15 @@ impl UltraLogApp { // Apply fonts cc.egui_ctx.set_fonts(fonts); - Self::default() + // Load user settings and set locale + let user_settings = UserSettings::load(); + rust_i18n::set_locale(user_settings.language.locale_code()); + + Self { + user_settings: user_settings.clone(), + language: user_settings.language, + ..Self::default() + } } // ======================================================================== diff --git a/src/i18n.rs b/src/i18n.rs new file mode 100644 index 0000000..69b5ee0 --- /dev/null +++ b/src/i18n.rs @@ -0,0 +1,94 @@ +//! Internationalization support for UltraLog. +//! +//! This module provides language selection and locale management. + +use serde::{Deserialize, Serialize}; + +/// Supported application languages +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum Language { + #[default] + English, + Spanish, + German, + French, + Italian, + #[serde(rename = "PortugueseBrazil")] + PortugueseBrazil, + #[serde(rename = "PortuguesePortugal")] + PortuguesePortugal, + #[serde(rename = "ChineseSimplified")] + ChineseSimplified, + Hindi, + Arabic, + Bengali, + Russian, + Urdu, + Indonesian, + Japanese, +} + +impl Language { + /// Get the locale code for rust-i18n + pub fn locale_code(&self) -> &'static str { + match self { + Language::English => "en", + Language::Spanish => "es", + Language::German => "de", + Language::French => "fr", + Language::Italian => "it", + Language::PortugueseBrazil => "pt-BR", + Language::PortuguesePortugal => "pt-PT", + Language::ChineseSimplified => "zh-CN", + Language::Hindi => "hi", + Language::Arabic => "ar", + Language::Bengali => "bn", + Language::Russian => "ru", + Language::Urdu => "ur", + Language::Indonesian => "id", + Language::Japanese => "ja", + } + } + + /// Get the display name for the language (in its native language) + pub fn display_name(&self) -> &'static str { + match self { + Language::English => "English", + Language::Spanish => "Español", + Language::German => "Deutsch", + Language::French => "Français", + Language::Italian => "Italiano", + Language::PortugueseBrazil => "Português (Brasil)", + Language::PortuguesePortugal => "Português (Portugal)", + Language::ChineseSimplified => "简体中文", + Language::Hindi => "हिन्दी", + Language::Arabic => "العربية", + Language::Bengali => "বাংলা", + Language::Russian => "Русский", + Language::Urdu => "اردو", + Language::Indonesian => "Bahasa Indonesia", + Language::Japanese => "日本語", + } + } + + /// Get all available languages + pub fn all() -> &'static [Language] { + &[ + Language::English, + Language::Spanish, + Language::German, + Language::French, + Language::Italian, + Language::PortugueseBrazil, + Language::PortuguesePortugal, + Language::ChineseSimplified, + Language::Hindi, + Language::Arabic, + Language::Bengali, + Language::Russian, + Language::Urdu, + Language::Indonesian, + Language::Japanese, + ] + } +} diff --git a/src/lib.rs b/src/lib.rs index 6e3ff55..3ad7c2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,8 @@ //! - [`updater`] - Auto-update functionality for checking and downloading updates //! - [`analytics`] - Anonymous usage analytics via PostHog //! - [`analysis`] - Signal processing and statistical analysis algorithms +//! - [`mod@i18n`] - Internationalization support +//! - [`settings`] - User settings persistence //! - [`ui`] - User interface components //! - `sidebar` - File list and view options //! - `channels` - Channel selection and display @@ -22,13 +24,22 @@ //! - `toast` - Toast notification system //! - `icons` - Custom icon drawing utilities +#[macro_use] +extern crate rust_i18n; + +// Initialize i18n with translation files from the i18n directory +// Fallback to English if a translation is missing +i18n!("i18n", fallback = "en"); + pub mod analysis; pub mod analytics; pub mod app; pub mod computed; pub mod expression; +pub mod i18n; pub mod normalize; pub mod parsers; +pub mod settings; pub mod state; pub mod ui; pub mod units; diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 0000000..fc97a27 --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,95 @@ +//! User settings persistence. +//! +//! This module handles loading and saving user preferences across sessions. + +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +use crate::i18n::Language; + +/// User settings that persist across sessions +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct UserSettings { + /// Settings file version for migration support + #[serde(default = "default_version")] + pub version: u32, + /// Selected language + #[serde(default)] + pub language: Language, +} + +fn default_version() -> u32 { + 1 +} + +impl Default for UserSettings { + fn default() -> Self { + Self { + version: 1, + language: Language::default(), + } + } +} + +impl UserSettings { + /// Get the config directory path for UltraLog + pub fn get_config_dir() -> Option { + #[cfg(target_os = "macos")] + { + dirs::data_dir().map(|p| p.join("UltraLog")) + } + #[cfg(target_os = "windows")] + { + dirs::config_dir().map(|p| p.join("UltraLog")) + } + #[cfg(target_os = "linux")] + { + dirs::config_dir().map(|p| p.join("ultralog")) + } + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] + { + dirs::config_dir().map(|p| p.join("ultralog")) + } + } + + /// Get the path to the settings JSON file + pub fn get_settings_path() -> Option { + Self::get_config_dir().map(|p| p.join("settings.json")) + } + + /// Load settings from disk + pub fn load() -> Self { + let path = match Self::get_settings_path() { + Some(p) => p, + None => return Self::default(), + }; + + if !path.exists() { + return Self::default(); + } + + match std::fs::read_to_string(&path) { + Ok(content) => serde_json::from_str(&content).unwrap_or_default(), + Err(_) => Self::default(), + } + } + + /// Save settings to disk + pub fn save(&self) -> Result<(), String> { + let path = Self::get_settings_path() + .ok_or_else(|| "Could not determine config directory".to_string())?; + + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent) + .map_err(|e| format!("Failed to create config directory: {}", e))?; + } + + let content = serde_json::to_string_pretty(self) + .map_err(|e| format!("Failed to serialize settings: {}", e))?; + + std::fs::write(&path, content) + .map_err(|e| format!("Failed to write settings file: {}", e))?; + + Ok(()) + } +} diff --git a/src/ui/analysis_panel.rs b/src/ui/analysis_panel.rs index df69db4..3898ef8 100644 --- a/src/ui/analysis_panel.rs +++ b/src/ui/analysis_panel.rs @@ -5,6 +5,7 @@ //! Features category tabs for filtering and clear separation between tools and results. use eframe::egui; +use rust_i18n::t; use crate::analysis::{AnalysisResult, Analyzer, AnalyzerConfig, LogDataAccess}; use crate::app::UltraLogApp; @@ -41,15 +42,8 @@ enum ParamType { Boolean, } -/// Category labels for the tab bar -/// First element is the category ID (must match analyzer.category()), second is display name -const CATEGORIES: &[(&str, &str)] = &[ - ("all", "All"), - ("Filters", "Filters"), - ("Statistics", "Statistics"), - ("AFR", "AFR"), - ("Derived", "Derived"), -]; +/// Category IDs for the tab bar (must match analyzer.category()) +const CATEGORY_IDS: &[&str] = &["all", "Filters", "Statistics", "AFR", "Derived"]; impl UltraLogApp { /// Render the analysis panel window @@ -60,7 +54,7 @@ impl UltraLogApp { let mut open = true; - egui::Window::new("Analysis Tools") + egui::Window::new(t!("analysis.window_title")) .open(&mut open) .resizable(true) .default_width(550.0) @@ -74,13 +68,13 @@ impl UltraLogApp { ui.vertical_centered(|ui| { ui.add_space(40.0); ui.label( - egui::RichText::new("No log file loaded") + egui::RichText::new(t!("analysis.no_file_loaded")) .color(egui::Color32::GRAY) .size(16.0), ); ui.add_space(8.0); ui.label( - egui::RichText::new("Load a log file to access analysis tools.") + egui::RichText::new(t!("analysis.load_file_help")) .color(egui::Color32::GRAY) .small(), ); @@ -107,13 +101,23 @@ impl UltraLogApp { /// Render the category filter tabs fn render_category_tabs(&mut self, ui: &mut egui::Ui) { ui.horizontal(|ui| { - for (category_id, display_name) in CATEGORIES { + for category_id in CATEGORY_IDS { let is_selected = match &self.analysis_selected_category { None => *category_id == "all", Some(cat) => cat == *category_id, }; - let text = egui::RichText::new(*display_name); + // Get translated category name + let display_name = match *category_id { + "all" => t!("analysis.category_all"), + "Filters" => t!("analysis.category_filters"), + "Statistics" => t!("analysis.category_statistics"), + "AFR" => t!("analysis.category_afr"), + "Derived" => t!("analysis.category_derived"), + _ => std::borrow::Cow::Borrowed(*category_id), + }; + + let text = egui::RichText::new(display_name.as_ref()); let text = if is_selected { text.strong() } else { @@ -214,17 +218,20 @@ impl UltraLogApp { // Results header with count ui.horizontal(|ui| { ui.label( - egui::RichText::new(format!("Results ({})", results.len())) - .strong() - .size(15.0), + egui::RichText::new(t!( + "analysis.results_count", + count = results.len() + )) + .strong() + .size(15.0), ); ui.with_layout( egui::Layout::right_to_left(egui::Align::Center), |ui| { if ui - .small_button("Clear All") - .on_hover_text("Remove all results") + .small_button(t!("analysis.clear_all")) + .on_hover_text(t!("analysis.remove_all_tooltip")) .clicked() { // Mark for clearing @@ -257,8 +264,17 @@ impl UltraLogApp { // Analyzers section header let category_label = match &self.analysis_selected_category { - None => "All Tools".to_string(), - Some(cat) => cat.clone(), + None => t!("analysis.all_tools").to_string(), + Some(cat) => { + // Translate the category name + match cat.as_str() { + "Filters" => t!("analysis.category_filters").to_string(), + "Statistics" => t!("analysis.category_statistics").to_string(), + "AFR" => t!("analysis.category_afr").to_string(), + "Derived" => t!("analysis.category_derived").to_string(), + _ => cat.clone(), + } + } }; ui.horizontal(|ui| { @@ -277,8 +293,7 @@ impl UltraLogApp { if filtered_infos.is_empty() { ui.label( - egui::RichText::new("No analyzers in this category") - .color(egui::Color32::GRAY), + egui::RichText::new(t!("analysis.no_analyzers")).color(egui::Color32::GRAY), ); } else { // Render analyzer cards @@ -394,7 +409,7 @@ impl UltraLogApp { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { // "Run & Chart" button (primary action) - let run_chart_btn = egui::Button::new("Run & Chart") + let run_chart_btn = egui::Button::new(t!("analysis.run_and_chart")) .fill(egui::Color32::from_rgb(60, 100, 60)); let run_chart_response = ui.add_enabled(channels_available, run_chart_btn); @@ -403,16 +418,15 @@ impl UltraLogApp { } if !channels_available { - run_chart_response.on_hover_text("Select valid channels first"); + run_chart_response.on_hover_text(t!("analysis.select_valid_channels")); } else { - run_chart_response - .on_hover_text("Run analysis and add result to chart"); + run_chart_response.on_hover_text(t!("analysis.run_add_tooltip")); } ui.add_space(4.0); // "Run" button (secondary - just adds to results) - let run_btn = egui::Button::new("Run"); + let run_btn = egui::Button::new(t!("analysis.run")); let run_response = ui.add_enabled(channels_available, run_btn); if run_response.clicked() { @@ -420,9 +434,9 @@ impl UltraLogApp { } if !channels_available { - run_response.on_hover_text("Select valid channels first"); + run_response.on_hover_text(t!("analysis.select_valid_channels")); } else { - run_response.on_hover_text("Run analysis (add to chart later)"); + run_response.on_hover_text(t!("analysis.run_tooltip")); } }); }); @@ -649,7 +663,7 @@ impl UltraLogApp { // Remove button if ui .small_button("x") - .on_hover_text("Remove result") + .on_hover_text(t!("analysis.remove_result_tooltip")) .clicked() { action = Some(ResultAction::Remove); @@ -659,8 +673,8 @@ impl UltraLogApp { // Add to chart button if ui - .button("+ Chart") - .on_hover_text("Add to chart as a channel") + .button(t!("analysis.add_chart")) + .on_hover_text(t!("analysis.add_to_chart_result")) .clicked() { action = Some(ResultAction::AddToChart); @@ -741,7 +755,7 @@ impl UltraLogApp { color_index, }); - self.show_toast_success(&format!("Added '{}' to chart", result.name)); + self.show_toast_success(&t!("toast.added_to_chart", name = result.name)); } /// Run an analyzer by its ID @@ -749,7 +763,7 @@ impl UltraLogApp { let file_idx = match self.selected_file { Some(idx) => idx, None => { - self.show_toast_error("No file selected"); + self.show_toast_error(&t!("toast.no_file_selected")); return; } }; @@ -758,7 +772,7 @@ impl UltraLogApp { let log = match self.files.get(file_idx) { Some(file) => &file.log, None => { - self.show_toast_error("File not found"); + self.show_toast_error(&t!("toast.file_not_found")); return; } }; @@ -778,14 +792,14 @@ impl UltraLogApp { .entry(file_idx) .or_default() .push(result); - self.show_toast_success(&format!("Analysis complete: {}", result_name)); + self.show_toast_success(&t!("toast.analysis_complete", name = result_name)); } Err(e) => { - self.show_toast_error(&format!("Analysis failed: {}", e)); + self.show_toast_error(&t!("toast.analysis_failed", error = e.to_string())); } } } else { - self.show_toast_error(&format!("Analyzer not found: {}", analyzer_id)); + self.show_toast_error(&t!("toast.analyzer_not_found", id = analyzer_id)); } } @@ -794,7 +808,7 @@ impl UltraLogApp { let file_idx = match self.selected_file { Some(idx) => idx, None => { - self.show_toast_error("No file selected"); + self.show_toast_error(&t!("toast.no_file_selected")); return; } }; @@ -803,7 +817,7 @@ impl UltraLogApp { let log = match self.files.get(file_idx) { Some(file) => &file.log, None => { - self.show_toast_error("File not found"); + self.show_toast_error(&t!("toast.file_not_found")); return; } }; @@ -835,14 +849,14 @@ impl UltraLogApp { // Immediately add to chart self.add_analysis_result_to_chart(result_idx); - self.show_toast_success(&format!("'{}' added to chart", result_name)); + self.show_toast_success(&t!("toast.added_to_chart", name = result_name)); } Err(e) => { - self.show_toast_error(&format!("Analysis failed: {}", e)); + self.show_toast_error(&t!("toast.analysis_failed", error = e.to_string())); } } } else { - self.show_toast_error(&format!("Analyzer not found: {}", analyzer_id)); + self.show_toast_error(&t!("toast.analyzer_not_found", id = analyzer_id)); } } } diff --git a/src/ui/channels_panel.rs b/src/ui/channels_panel.rs index 18b43b8..3e4091b 100644 --- a/src/ui/channels_panel.rs +++ b/src/ui/channels_panel.rs @@ -3,6 +3,7 @@ //! This panel provides channel selection functionality that works across all tool modes. use eframe::egui; +use rust_i18n::t; use crate::app::UltraLogApp; use crate::normalize::sort_channels_by_priority; @@ -42,12 +43,12 @@ impl UltraLogApp { .show(ui, |ui| { ui.horizontal(|ui| { ui.label( - egui::RichText::new("ƒ") + egui::RichText::new("\u{0192}") .color(egui::Color32::WHITE) .size(font_14), ); ui.label( - egui::RichText::new("Computed Channels") + egui::RichText::new(t!("channels.computed_channels")) .color(egui::Color32::WHITE) .size(font_14), ); @@ -57,7 +58,7 @@ impl UltraLogApp { if computed_btn .response .interact(egui::Sense::click()) - .on_hover_text("Create virtual channels from mathematical formulas") + .on_hover_text(t!("channels.computed_channels_tooltip")) .clicked() { self.show_computed_channels_manager = true; @@ -79,13 +80,13 @@ impl UltraLogApp { .show(ui, |ui| { ui.horizontal(|ui| { ui.label( - egui::RichText::new("🔍") + egui::RichText::new("\u{1F50D}") .size(font_14) .color(egui::Color32::GRAY), ); let response = ui.add( egui::TextEdit::singleline(&mut search_text) - .hint_text("Search channels...") + .hint_text(t!("channels.search_hint")) .desired_width(f32::INFINITY) .frame(false), ); @@ -101,9 +102,11 @@ impl UltraLogApp { // Channel count ui.label( - egui::RichText::new(format!( - "Selected: {} / {} | Total: {}", - selected_count, MAX_CHANNELS, channel_count + egui::RichText::new(t!( + "channels.selected_count", + selected = selected_count, + max = MAX_CHANNELS, + total = channel_count )) .size(font_12) .color(egui::Color32::GRAY), @@ -120,19 +123,19 @@ impl UltraLogApp { ui.add_space(40.0); ui.vertical_centered(|ui| { ui.label( - egui::RichText::new("📊") + egui::RichText::new("\u{1F4CA}") .size(32.0) .color(egui::Color32::from_rgb(100, 100, 100)), ); ui.add_space(8.0); ui.label( - egui::RichText::new("No file selected") + egui::RichText::new(t!("channels.no_file_selected")) .size(font_14) .color(egui::Color32::GRAY), ); ui.add_space(4.0); ui.label( - egui::RichText::new("Load a file to view channels") + egui::RichText::new(t!("channels.load_file_to_view")) .size(font_12) .color(egui::Color32::from_rgb(100, 100, 100)), ); @@ -209,7 +212,7 @@ impl UltraLogApp { let response = frame .show(ui, |ui| { ui.horizontal(|ui| { - let check = if is_selected { "☑" } else { "☐" }; + let check = if is_selected { "\u{2611}" } else { "\u{2610}" }; ui.label(egui::RichText::new(check).size(font_14).color(text_color)); ui.label( egui::RichText::new(display_name) @@ -240,9 +243,12 @@ impl UltraLogApp { // Channels with data if !channels_with.is_empty() { egui::CollapsingHeader::new( - egui::RichText::new(format!("📊 With Data ({})", channels_with.len())) - .size(font_14) - .strong(), + egui::RichText::new(format!( + "\u{1F4CA} {}", + t!("channels.with_data", count = channels_with.len()) + )) + .size(font_14) + .strong(), ) .default_open(true) .show(ui, |ui| { @@ -264,9 +270,12 @@ impl UltraLogApp { // Empty channels if !channels_without.is_empty() { egui::CollapsingHeader::new( - egui::RichText::new(format!("📭 Empty ({})", channels_without.len())) - .size(font_14) - .color(egui::Color32::GRAY), + egui::RichText::new(format!( + "\u{1F4ED} {}", + t!("channels.empty", count = channels_without.len()) + )) + .size(font_14) + .color(egui::Color32::GRAY), ) .default_open(false) .show(ui, |ui| { diff --git a/src/ui/chart.rs b/src/ui/chart.rs index 7d98d20..2dd825b 100644 --- a/src/ui/chart.rs +++ b/src/ui/chart.rs @@ -2,6 +2,7 @@ use eframe::egui; use egui_plot::{Line, Plot, PlotBounds, PlotPoints, VLine}; +use rust_i18n::t; use crate::app::UltraLogApp; use crate::normalize::normalize_channel_name_with_custom; @@ -16,7 +17,7 @@ impl UltraLogApp { if selected_channels.is_empty() { ui.centered_and_justified(|ui| { ui.label( - egui::RichText::new("Select channels to display chart") + egui::RichText::new(t!("chart.select_channels")) .size(self.scaled_font(20.0)) .color(egui::Color32::GRAY), ); diff --git a/src/ui/computed_channels_manager.rs b/src/ui/computed_channels_manager.rs index 585501c..3c3d89a 100644 --- a/src/ui/computed_channels_manager.rs +++ b/src/ui/computed_channels_manager.rs @@ -4,6 +4,7 @@ //! and apply computed channels to the active log file. use eframe::egui; +use rust_i18n::t; use crate::app::UltraLogApp; use crate::computed::{ComputedChannel, ComputedChannelTemplate}; @@ -26,7 +27,7 @@ impl UltraLogApp { let font_12 = self.scaled_font(12.0); let font_14 = self.scaled_font(14.0); - egui::Window::new("Computed Channels") + egui::Window::new(t!("computed.title")) .open(&mut open) .resizable(true) .default_width(500.0) @@ -35,7 +36,7 @@ impl UltraLogApp { .show(ctx, |ui| { // Header with help button ui.horizontal(|ui| { - ui.heading("Computed Channels"); + ui.heading(t!("computed.title")); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { // Help button let help_btn = ui.add( @@ -45,9 +46,9 @@ impl UltraLogApp { if help_btn.clicked() { self.show_computed_channels_help = !self.show_computed_channels_help; } - help_btn.on_hover_text("Show formula syntax help"); + help_btn.on_hover_text(t!("computed.show_help")); - if ui.button("+ New").clicked() { + if ui.button(t!("computed.new")).clicked() { self.formula_editor_state.open_new(); } }); @@ -55,7 +56,7 @@ impl UltraLogApp { ui.add_space(4.0); ui.label( - egui::RichText::new("Create virtual channels from mathematical formulas.") + egui::RichText::new(t!("computed.description")) .size(font_12) .color(egui::Color32::GRAY), ); @@ -63,43 +64,47 @@ impl UltraLogApp { ui.add_space(8.0); // Quick Create section - ui.label(egui::RichText::new("Quick Create:").size(font_12).strong()); + ui.label( + egui::RichText::new(t!("computed.quick_create")) + .size(font_12) + .strong(), + ); ui.add_space(4.0); ui.horizontal(|ui| { if ui - .button("Rate of Change") - .on_hover_text("Create: Channel - Channel[-1]") + .button(t!("computed.rate_of_change")) + .on_hover_text(t!("computed.rate_of_change_hint")) .clicked() { self.formula_editor_state.open_with_pattern( - "Rate of Change", + &t!("computed.rate_of_change"), "{channel} - {channel}[-1]", "/sample", - "Rate of change per sample", + &t!("computed.rate_of_change_desc"), ); } if ui - .button("Moving Avg") - .on_hover_text("Create: 3-sample moving average") + .button(t!("computed.moving_avg")) + .on_hover_text(t!("computed.moving_avg_hint")) .clicked() { self.formula_editor_state.open_with_pattern( - "Moving Average", + &t!("computed.moving_avg"), "({channel} + {channel}[-1] + {channel}[-2]) / 3", "", - "3-sample moving average for smoothing", + &t!("computed.moving_avg_desc"), ); } if ui - .button("% Deviation") - .on_hover_text("Create: Percentage deviation from a target") + .button(t!("computed.deviation")) + .on_hover_text(t!("computed.deviation_hint")) .clicked() { self.formula_editor_state.open_with_pattern( - "Deviation", + &t!("computed.deviation"), "({channel} - 14.7) / 14.7 * 100", "%", - "Percentage deviation from target value", + &t!("computed.deviation_desc"), ); } }); @@ -111,9 +116,9 @@ impl UltraLogApp { // Search filter ui.horizontal(|ui| { ui.label( - egui::RichText::new(format!( - "Your Library ({})", - self.computed_library.templates.len() + egui::RichText::new(t!( + "computed.your_library", + count = self.computed_library.templates.len() )) .size(font_14) .strong(), @@ -121,7 +126,7 @@ impl UltraLogApp { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { ui.add( egui::TextEdit::singleline(&mut self.computed_channels_search) - .hint_text("🔍 Search...") + .hint_text(t!("computed.search")) .desired_width(120.0), ); }); @@ -134,12 +139,12 @@ impl UltraLogApp { ui.vertical_centered(|ui| { ui.add_space(20.0); ui.label( - egui::RichText::new("No computed channels yet") + egui::RichText::new(t!("computed.no_channels")) .color(egui::Color32::GRAY), ); ui.add_space(4.0); ui.label( - egui::RichText::new("Use Quick Create or click '+ New' to get started") + egui::RichText::new(t!("computed.get_started")) .color(egui::Color32::GRAY) .size(font_12), ); @@ -204,11 +209,11 @@ impl UltraLogApp { if let Some(ref template) = template_to_duplicate { let mut new_template = template.clone(); new_template.id = uuid::Uuid::new_v4().to_string(); - new_template.name = format!("{} (copy)", template.name); + new_template.name = format!("{} ({})", template.name, t!("computed.copy")); new_template.is_builtin = false; self.computed_library.add_template(new_template); let _ = self.computed_library.save(); - self.show_toast_success("Template duplicated"); + self.show_toast_success(&t!("toast.template_duplicated")); } } @@ -276,7 +281,7 @@ impl UltraLogApp { .size(font_12) .color(egui::Color32::GOLD), ) - .on_hover_text("Built-in template"); + .on_hover_text(t!("computed.builtin_template")); } }); ui.label( @@ -291,18 +296,18 @@ impl UltraLogApp { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { // Overflow menu for Edit/Delete/Duplicate ui.menu_button("•••", |ui| { - if ui.button("Edit").clicked() { + if ui.button(t!("common.edit")).clicked() { *template_to_edit = Some(template.id.clone()); ui.close(); } - if ui.button("Duplicate").clicked() { + if ui.button(t!("common.duplicate")).clicked() { *template_to_duplicate = Some(template.clone()); ui.close(); } ui.separator(); if ui .button( - egui::RichText::new("Delete") + egui::RichText::new(t!("common.delete")) .color(egui::Color32::from_rgb(255, 120, 120)), ) .clicked() @@ -315,7 +320,7 @@ impl UltraLogApp { // Apply button (primary action) if self.active_tab.is_some() { let apply_btn = egui::Button::new( - egui::RichText::new("Apply").color(egui::Color32::WHITE), + egui::RichText::new(t!("common.apply")).color(egui::Color32::WHITE), ) .fill(egui::Color32::from_rgb(80, 110, 80)); @@ -339,7 +344,7 @@ impl UltraLogApp { .unwrap_or(0); ui.label( - egui::RichText::new(format!("Applied to Current File ({})", applied_count)) + egui::RichText::new(t!("computed.applied_to_file", count = applied_count)) .size(font_14) .strong(), ); @@ -347,7 +352,7 @@ impl UltraLogApp { if applied_count == 0 { ui.label( - egui::RichText::new("Apply templates from the library above") + egui::RichText::new(t!("computed.apply_templates_hint")) .color(egui::Color32::GRAY) .size(font_12), ); @@ -397,15 +402,15 @@ impl UltraLogApp { |ui| { if ui .small_button("x") - .on_hover_text("Remove") + .on_hover_text(t!("common.remove")) .clicked() { channel_to_remove = Some(idx); } if channel.is_valid() && ui - .small_button("+ Chart") - .on_hover_text("Add to chart") + .small_button(t!("computed.add_chart")) + .on_hover_text(t!("computed.add_to_chart")) .clicked() { channel_to_select = Some(idx); @@ -428,7 +433,7 @@ impl UltraLogApp { } } else { ui.label( - egui::RichText::new("Load a log file to apply computed channels") + egui::RichText::new(t!("computed.load_file_hint")) .color(egui::Color32::GRAY) .size(font_12), ); @@ -439,7 +444,7 @@ impl UltraLogApp { fn render_computed_channels_help(&mut self, ctx: &egui::Context) { let mut open = true; - egui::Window::new("Formula Help") + egui::Window::new(t!("computed.help_title")) .open(&mut open) .resizable(false) .default_width(400.0) @@ -450,49 +455,55 @@ impl UltraLogApp { .max_height(400.0) .show(ui, |ui| { // Channel References - ui.label(egui::RichText::new("Channel References").strong()); + ui.label(egui::RichText::new(t!("computed.help_channel_refs")).strong()); ui.add_space(4.0); - Self::help_row(ui, "RPM", "Current value of RPM channel"); + Self::help_row(ui, "RPM", &t!("computed.help_current_value")); Self::help_row( ui, "\"Manifold Pressure\"", - "Channels with spaces (use quotes)", + &t!("computed.help_quoted_channel"), ); - Self::help_row(ui, "RPM[-1]", "Previous sample (index offset)"); - Self::help_row(ui, "RPM[+2]", "2 samples ahead"); - Self::help_row(ui, "RPM@-0.1s", "Value 100ms ago (time offset)"); + Self::help_row(ui, "RPM[-1]", &t!("computed.help_prev_sample")); + Self::help_row(ui, "RPM[+2]", &t!("computed.help_samples_ahead")); + Self::help_row(ui, "RPM@-0.1s", &t!("computed.help_time_offset")); ui.add_space(12.0); - ui.label(egui::RichText::new("Operators").strong()); + ui.label(egui::RichText::new(t!("computed.help_operators")).strong()); ui.add_space(4.0); - Self::help_row(ui, "+ - * /", "Basic math"); - Self::help_row(ui, "^", "Power (e.g., RPM^2)"); - Self::help_row(ui, "( )", "Grouping"); + Self::help_row(ui, "+ - * /", &t!("computed.help_basic_math")); + Self::help_row(ui, "^", &t!("computed.help_power")); + Self::help_row(ui, "( )", &t!("computed.help_grouping")); ui.add_space(12.0); - ui.label(egui::RichText::new("Functions").strong()); + ui.label(egui::RichText::new(t!("computed.help_functions")).strong()); ui.add_space(4.0); - Self::help_row(ui, "sin, cos, tan", "Trigonometry"); - Self::help_row(ui, "sqrt, abs", "Square root, absolute value"); - Self::help_row(ui, "ln, log, exp", "Logarithms, exponential"); - Self::help_row(ui, "min, max", "Minimum, maximum"); - Self::help_row(ui, "floor, ceil", "Rounding"); + Self::help_row(ui, "sin, cos, tan", &t!("computed.help_trig")); + Self::help_row(ui, "sqrt, abs", &t!("computed.help_sqrt_abs")); + Self::help_row(ui, "ln, log, exp", &t!("computed.help_log")); + Self::help_row(ui, "min, max", &t!("computed.help_minmax")); + Self::help_row(ui, "floor, ceil", &t!("computed.help_rounding")); ui.add_space(12.0); - ui.label( - egui::RichText::new("Statistics (for anomaly detection)").strong(), - ); + ui.label(egui::RichText::new(t!("computed.help_statistics")).strong()); ui.add_space(4.0); - Self::help_row(ui, "_mean_RPM", "Mean of entire RPM channel"); - Self::help_row(ui, "_stdev_RPM", "Standard deviation"); - Self::help_row(ui, "_min_RPM / _max_RPM", "Min/max values"); + Self::help_row(ui, "_mean_RPM", &t!("computed.help_mean")); + Self::help_row(ui, "_stdev_RPM", &t!("computed.help_stdev")); + Self::help_row(ui, "_min_RPM / _max_RPM", &t!("computed.help_min_max")); ui.add_space(12.0); - ui.label(egui::RichText::new("Examples").strong()); + ui.label(egui::RichText::new(t!("computed.help_examples")).strong()); ui.add_space(4.0); - Self::example_row(ui, "RPM - RPM[-1]", "RPM change per sample"); - Self::example_row(ui, "(AFR - 14.7) / 14.7 * 100", "AFR % deviation"); - Self::example_row(ui, "(RPM - _mean_RPM) / _stdev_RPM", "Z-score"); + Self::example_row(ui, "RPM - RPM[-1]", &t!("computed.help_ex_rate")); + Self::example_row( + ui, + "(AFR - 14.7) / 14.7 * 100", + &t!("computed.help_ex_deviation"), + ); + Self::example_row( + ui, + "(RPM - _mean_RPM) / _stdev_RPM", + &t!("computed.help_ex_zscore"), + ); }); }); @@ -531,7 +542,7 @@ impl UltraLogApp { /// Apply a computed channel template to the current file pub fn apply_computed_channel_template(&mut self, template: &ComputedChannelTemplate) { let Some(tab_idx) = self.active_tab else { - self.show_toast_warning("No active tab"); + self.show_toast_warning(&t!("toast.no_active_tab")); return; }; @@ -546,7 +557,7 @@ impl UltraLogApp { let bindings = match build_channel_bindings(&refs, &available_channels) { Ok(b) => b, Err(e) => { - self.show_toast_error(&format!("Failed to apply: {}", e)); + self.show_toast_error(&t!("toast.failed_to_apply", error = e)); return; } }; @@ -572,7 +583,7 @@ impl UltraLogApp { ) { Ok(data) => Some(data), Err(e) => { - self.show_toast_error(&format!("Evaluation failed: {}", e)); + self.show_toast_error(&t!("toast.evaluation_failed", error = e)); return; } } @@ -585,7 +596,7 @@ impl UltraLogApp { ) { Ok(data) => Some(data), Err(e) => { - self.show_toast_error(&format!("Evaluation failed: {}", e)); + self.show_toast_error(&t!("toast.evaluation_failed", error = e)); return; } } @@ -602,7 +613,7 @@ impl UltraLogApp { .or_default() .push(channel); - self.show_toast_success(&format!("Applied '{}'", template.name)); + self.show_toast_success(&t!("toast.applied_template", name = template.name.as_str())); } /// Add a computed channel to the chart @@ -633,13 +644,13 @@ impl UltraLogApp { .iter() .any(|c| c.file_index == file_idx && c.channel_index == virtual_channel_index) { - self.show_toast_warning("Channel already on chart"); + self.show_toast_warning(&t!("toast.channel_already_on_chart")); return; } // Check max channels if self.tabs[tab_idx].selected_channels.len() >= 10 { - self.show_toast_warning("Maximum 10 channels reached"); + self.show_toast_warning(&t!("toast.max_channels_reached")); return; } @@ -667,6 +678,6 @@ impl UltraLogApp { color_index, }); - self.show_toast_success(&format!("Added '{}' to chart", computed.name())); + self.show_toast_success(&t!("toast.added_to_chart", name = computed.name())); } } diff --git a/src/ui/export.rs b/src/ui/export.rs index 1eaa21d..4f0f11a 100644 --- a/src/ui/export.rs +++ b/src/ui/export.rs @@ -2,6 +2,7 @@ use printpdf::path::{PaintMode, WindingOrder}; use printpdf::*; +use rust_i18n::t; use std::fs::File; use std::io::BufWriter; @@ -29,9 +30,9 @@ impl UltraLogApp { match self.render_chart_to_png(&path) { Ok(_) => { analytics::track_export("png"); - self.show_toast_success("Chart exported as PNG"); + self.show_toast_success(&t!("toast.export_png_success")); } - Err(e) => self.show_toast_error(&format!("Export failed: {}", e)), + Err(e) => self.show_toast_error(&t!("toast.export_failed", error = e.to_string())), } } @@ -49,9 +50,9 @@ impl UltraLogApp { match self.render_chart_to_pdf(&path) { Ok(_) => { analytics::track_export("pdf"); - self.show_toast_success("Chart exported as PDF"); + self.show_toast_success(&t!("toast.export_pdf_success")); } - Err(e) => self.show_toast_error(&format!("Export failed: {}", e)), + Err(e) => self.show_toast_error(&t!("toast.export_failed", error = e.to_string())), } } @@ -369,9 +370,9 @@ impl UltraLogApp { match self.render_histogram_to_png(&path) { Ok(_) => { analytics::track_export("histogram_png"); - self.show_toast_success("Histogram exported as PNG"); + self.show_toast_success(&t!("toast.histogram_exported_png")); } - Err(e) => self.show_toast_error(&format!("Export failed: {}", e)), + Err(e) => self.show_toast_error(&t!("toast.export_failed", error = e.to_string())), } } @@ -389,9 +390,9 @@ impl UltraLogApp { match self.render_scatter_plot_to_png(&path) { Ok(_) => { analytics::track_export("scatter_plot_png"); - self.show_toast_success("Scatter plot exported as PNG"); + self.show_toast_success(&t!("toast.scatter_exported_png")); } - Err(e) => self.show_toast_error(&format!("Export failed: {}", e)), + Err(e) => self.show_toast_error(&t!("toast.export_failed", error = e.to_string())), } } @@ -409,9 +410,9 @@ impl UltraLogApp { match self.render_scatter_plot_to_pdf(&path) { Ok(_) => { analytics::track_export("scatter_plot_pdf"); - self.show_toast_success("Scatter plot exported as PDF"); + self.show_toast_success(&t!("toast.scatter_exported_pdf")); } - Err(e) => self.show_toast_error(&format!("Export failed: {}", e)), + Err(e) => self.show_toast_error(&t!("toast.export_failed", error = e.to_string())), } } @@ -429,9 +430,9 @@ impl UltraLogApp { match self.render_histogram_to_pdf(&path) { Ok(_) => { analytics::track_export("histogram_pdf"); - self.show_toast_success("Histogram exported as PDF"); + self.show_toast_success(&t!("toast.histogram_exported_pdf")); } - Err(e) => self.show_toast_error(&format!("Export failed: {}", e)), + Err(e) => self.show_toast_error(&t!("toast.export_failed", error = e.to_string())), } } diff --git a/src/ui/files_panel.rs b/src/ui/files_panel.rs index 5d3a67e..ad0f2d5 100644 --- a/src/ui/files_panel.rs +++ b/src/ui/files_panel.rs @@ -1,6 +1,7 @@ //! Files panel - file management, loading, and file list. use eframe::egui; +use rust_i18n::t; use crate::app::UltraLogApp; use crate::state::{LoadingState, SUPPORTED_EXTENSIONS}; @@ -13,7 +14,7 @@ impl UltraLogApp { if let LoadingState::Loading(filename) = &self.loading_state { ui.horizontal(|ui| { ui.spinner(); - ui.label(format!("Loading {}...", filename)); + ui.label(t!("files.loading", filename = filename)); }); ui.add_space(8.0); ui.separator(); @@ -60,7 +61,7 @@ impl UltraLogApp { .collect(); ui.label( - egui::RichText::new(format!("Loaded Files ({})", file_info.len())) + egui::RichText::new(t!("files.loaded_files", count = file_info.len())) .size(self.scaled_font(13.0)) .color(egui::Color32::GRAY), ); @@ -135,9 +136,11 @@ impl UltraLogApp { // ECU type and data info ui.label( - egui::RichText::new(format!( - "{} • {} ch • {} pts", - ecu_name, channel_count, data_count + egui::RichText::new(t!( + "files.file_info", + ecu = ecu_name, + channels = channel_count, + points = data_count )) .size(self.scaled_font(11.0)) .color(egui::Color32::GRAY), @@ -173,7 +176,7 @@ impl UltraLogApp { .size(self.scaled_font(16.0)), ); ui.label( - egui::RichText::new("Add File") + egui::RichText::new(t!("files.add_file")) .color(egui::Color32::WHITE) .size(self.scaled_font(14.0)), ); @@ -230,7 +233,7 @@ impl UltraLogApp { .inner_margin(egui::vec2(16.0, 8.0)) .show(ui, |ui| { ui.label( - egui::RichText::new("Select a file") + egui::RichText::new(t!("files.select_file")) .color(egui::Color32::WHITE) .size(self.scaled_font(14.0)), ); @@ -256,7 +259,7 @@ impl UltraLogApp { ui.add_space(12.0); ui.label( - egui::RichText::new("or") + egui::RichText::new(t!("files.or")) .color(text_gray) .size(self.scaled_font(12.0)), ); @@ -264,7 +267,7 @@ impl UltraLogApp { ui.add_space(8.0); ui.label( - egui::RichText::new("Drop file here") + egui::RichText::new(t!("files.drop_file_here")) .color(egui::Color32::LIGHT_GRAY) .size(self.scaled_font(13.0)), ); @@ -275,7 +278,7 @@ impl UltraLogApp { .iter() .map(|ext| ext.to_uppercase()) .collect::>() - .join(" • "); + .join(" \u{2022} "); ui.label( egui::RichText::new(extensions_text) .color(text_gray) diff --git a/src/ui/formula_editor.rs b/src/ui/formula_editor.rs index 7cf69b7..c949f8a 100644 --- a/src/ui/formula_editor.rs +++ b/src/ui/formula_editor.rs @@ -4,6 +4,7 @@ //! Features an expanded channel browser, quick pattern buttons, and rich preview with statistics. use eframe::egui; +use rust_i18n::t; use crate::app::UltraLogApp; use crate::computed::ComputedChannelTemplate; @@ -22,9 +23,9 @@ impl UltraLogApp { let mut should_save = false; let title = if self.formula_editor_state.is_editing() { - "Edit Computed Channel" + t!("formula.edit_computed_channel") } else { - "New Computed Channel" + t!("formula.new_computed_channel") }; egui::Window::new(title) @@ -39,10 +40,10 @@ impl UltraLogApp { // Name field ui.horizontal(|ui| { - ui.label("Name:"); + ui.label(t!("formula.name")); ui.add( egui::TextEdit::singleline(&mut self.formula_editor_state.name) - .hint_text("e.g., RPM Delta") + .hint_text(t!("formula.name_hint")) .desired_width(300.0), ); }); @@ -50,10 +51,10 @@ impl UltraLogApp { ui.add_space(8.0); // Formula field - ui.label("Formula:"); + ui.label(t!("formula.formula")); let formula_response = ui.add( egui::TextEdit::multiline(&mut self.formula_editor_state.formula) - .hint_text("e.g., RPM - RPM[-1]") + .hint_text(t!("formula.formula_hint")) .desired_width(ui.available_width()) .desired_rows(3) .font(egui::TextStyle::Monospace), @@ -67,7 +68,7 @@ impl UltraLogApp { // Quick pattern buttons ui.add_space(4.0); ui.horizontal(|ui| { - ui.label(egui::RichText::new("Insert:").small().weak()); + ui.label(egui::RichText::new(t!("formula.insert")).small().weak()); ui.add_space(4.0); // Math operators @@ -82,7 +83,7 @@ impl UltraLogApp { ] { if ui .small_button(egui::RichText::new(label).monospace()) - .on_hover_text(format!("Insert {}", insert.trim())) + .on_hover_text(t!("formula.insert_tooltip", op = insert.trim())) .clicked() { self.formula_editor_state.formula.push_str(insert); @@ -93,18 +94,21 @@ impl UltraLogApp { ui.separator(); // Time-shift operators - for (label, insert, tooltip) in [ - ("[-1]", "[-1]", "Previous sample (index offset)"), - ("@-0.1s", "@-0.1s", "Value 0.1 seconds ago"), - ] { - if ui - .small_button(egui::RichText::new(label).monospace()) - .on_hover_text(tooltip) - .clicked() - { - self.formula_editor_state.formula.push_str(insert); - self.validate_current_formula(); - } + if ui + .small_button(egui::RichText::new("[-1]").monospace()) + .on_hover_text(t!("formula.prev_sample_tooltip")) + .clicked() + { + self.formula_editor_state.formula.push_str("[-1]"); + self.validate_current_formula(); + } + if ui + .small_button(egui::RichText::new("@-0.1s").monospace()) + .on_hover_text(t!("formula.time_ago_tooltip")) + .clicked() + { + self.formula_editor_state.formula.push_str("@-0.1s"); + self.validate_current_formula(); } }); @@ -112,30 +116,35 @@ impl UltraLogApp { ui.add_space(4.0); if let Some(error) = &self.formula_editor_state.validation_error { ui.horizontal(|ui| { - ui.label(egui::RichText::new("Error:").color(egui::Color32::RED)); + ui.label( + egui::RichText::new(t!("formula.error")).color(egui::Color32::RED), + ); ui.label(egui::RichText::new(error).color(egui::Color32::RED).small()); }); } else if !self.formula_editor_state.formula.is_empty() { - ui.label(egui::RichText::new("Formula valid").color(egui::Color32::GREEN)); + ui.label( + egui::RichText::new(t!("formula.formula_valid")) + .color(egui::Color32::GREEN), + ); } ui.add_space(8.0); // Unit and description in a row ui.horizontal(|ui| { - ui.label("Unit:"); + ui.label(t!("formula.unit")); ui.add( egui::TextEdit::singleline(&mut self.formula_editor_state.unit) - .hint_text("e.g., RPM/s") + .hint_text(t!("formula.unit_hint")) .desired_width(100.0), ); ui.add_space(16.0); - ui.label("Description:"); + ui.label(t!("formula.description")); ui.add( egui::TextEdit::singleline(&mut self.formula_editor_state.description) - .hint_text("Optional description") + .hint_text(t!("formula.description_hint")) .desired_width(ui.available_width() - 20.0), ); }); @@ -144,20 +153,18 @@ impl UltraLogApp { // Channel browser - expanded by default for discoverability if self.active_tab.is_some() { - egui::CollapsingHeader::new("Available Channels") + egui::CollapsingHeader::new(t!("formula.available_channels")) .default_open(true) // Expanded by default .show(ui, |ui| { let channels = self.get_available_channel_names(); if channels.is_empty() { ui.label( - egui::RichText::new( - "No channels available - load a log file first", - ) - .color(egui::Color32::GRAY), + egui::RichText::new(t!("formula.no_channels_available")) + .color(egui::Color32::GRAY), ); } else { ui.label( - egui::RichText::new("Click to insert into formula:") + egui::RichText::new(t!("formula.click_to_insert")) .small() .weak(), ); @@ -209,11 +216,11 @@ impl UltraLogApp { let avg = sum / valid_values.len() as f64; ui.horizontal(|ui| { - ui.label(egui::RichText::new("Preview").strong()); + ui.label(egui::RichText::new(t!("formula.preview")).strong()); ui.add_space(8.0); // Stats in a compact row - ui.label(egui::RichText::new("Min:").small().weak()); + ui.label(egui::RichText::new(t!("formula.min")).small().weak()); ui.label( egui::RichText::new(format!("{:.2}", min)) .monospace() @@ -221,7 +228,7 @@ impl UltraLogApp { ); ui.add_space(8.0); - ui.label(egui::RichText::new("Avg:").small().weak()); + ui.label(egui::RichText::new(t!("formula.avg")).small().weak()); ui.label( egui::RichText::new(format!("{:.2}", avg)) .monospace() @@ -229,7 +236,7 @@ impl UltraLogApp { ); ui.add_space(8.0); - ui.label(egui::RichText::new("Max:").small().weak()); + ui.label(egui::RichText::new(t!("formula.max")).small().weak()); ui.label( egui::RichText::new(format!("{:.2}", max)) .monospace() @@ -239,7 +246,7 @@ impl UltraLogApp { // Sample values ui.horizontal(|ui| { - ui.label(egui::RichText::new("Sample:").small().weak()); + ui.label(egui::RichText::new(t!("formula.sample")).small().weak()); for (i, val) in preview_values.iter().take(5).enumerate() { if i > 0 { ui.label(egui::RichText::new(",").weak()); @@ -264,7 +271,7 @@ impl UltraLogApp { // Buttons ui.horizontal(|ui| { - if ui.button("Cancel").clicked() { + if ui.button(t!("formula.cancel")).clicked() { self.formula_editor_state.close(); } @@ -274,13 +281,13 @@ impl UltraLogApp { && self.formula_editor_state.validation_error.is_none(); ui.add_enabled_ui(can_save, |ui| { - if ui.button("Save").clicked() { + if ui.button(t!("formula.save")).clicked() { should_save = true; } }); // Validate button - if ui.button("Validate").clicked() { + if ui.button(t!("formula.validate")).clicked() { self.validate_current_formula(); } }); @@ -371,9 +378,9 @@ impl UltraLogApp { // Save library if let Err(e) = self.computed_library.save() { - self.show_toast_error(&format!("Failed to save: {}", e)); + self.show_toast_error(&t!("toast.failed_to_save", error = e)); } else { - self.show_toast_success("Channel saved to library"); + self.show_toast_success(&t!("toast.channel_saved")); } self.formula_editor_state.close(); diff --git a/src/ui/histogram.rs b/src/ui/histogram.rs index c93ffd0..b1f72c2 100644 --- a/src/ui/histogram.rs +++ b/src/ui/histogram.rs @@ -5,6 +5,7 @@ //! cell coloring based on average Z-value or hit count. use eframe::egui; +use rust_i18n::t; use crate::app::UltraLogApp; use crate::normalize::sort_channels_by_priority; @@ -92,7 +93,7 @@ impl UltraLogApp { if self.active_tab.is_none() || self.files.is_empty() { ui.centered_and_justified(|ui| { ui.label( - egui::RichText::new("Load a log file to use histogram") + egui::RichText::new(t!("histogram.no_file_loaded")) .size(self.scaled_font(20.0)) .color(egui::Color32::GRAY), ); @@ -160,7 +161,7 @@ impl UltraLogApp { ui.horizontal(|ui| { // X Axis selector - ui.label(egui::RichText::new("X Axis:").size(font_15)); + ui.label(egui::RichText::new(t!("histogram.x_axis")).size(font_15)); egui::ComboBox::from_id_salt("histogram_x") .selected_text( egui::RichText::new( @@ -188,7 +189,7 @@ impl UltraLogApp { ui.add_space(16.0); // Y Axis selector - ui.label(egui::RichText::new("Y Axis:").size(font_15)); + ui.label(egui::RichText::new(t!("histogram.y_axis")).size(font_15)); egui::ComboBox::from_id_salt("histogram_y") .selected_text( egui::RichText::new( @@ -218,7 +219,7 @@ impl UltraLogApp { // Z Axis selector (only enabled in AverageZ mode) let z_enabled = current_mode == HistogramMode::AverageZ; ui.add_enabled_ui(z_enabled, |ui| { - ui.label(egui::RichText::new("Z Axis:").size(font_15)); + ui.label(egui::RichText::new(t!("histogram.z_axis")).size(font_15)); egui::ComboBox::from_id_salt("histogram_z") .selected_text( egui::RichText::new( @@ -247,7 +248,7 @@ impl UltraLogApp { ui.add_space(20.0); // Grid size selector - ui.label(egui::RichText::new("Grid:").size(font_15)); + ui.label(egui::RichText::new(t!("histogram.grid")).size(font_15)); egui::ComboBox::from_id_salt("histogram_grid_size") .selected_text(egui::RichText::new(current_grid_size.name()).size(font_14)) .width(80.0) @@ -273,11 +274,11 @@ impl UltraLogApp { ui.add_space(20.0); // Mode toggle - ui.label(egui::RichText::new("Mode:").size(font_15)); + ui.label(egui::RichText::new(t!("histogram.mode")).size(font_15)); if ui .selectable_label( current_mode == HistogramMode::AverageZ, - egui::RichText::new("Average Z").size(font_14), + egui::RichText::new(t!("histogram.average_z")).size(font_14), ) .clicked() { @@ -286,7 +287,7 @@ impl UltraLogApp { if ui .selectable_label( current_mode == HistogramMode::HitCount, - egui::RichText::new("Hit Count").size(font_14), + egui::RichText::new(t!("histogram.hit_count")).size(font_14), ) .clicked() { diff --git a/src/ui/menu.rs b/src/ui/menu.rs index 1bf10b7..9e75fe9 100644 --- a/src/ui/menu.rs +++ b/src/ui/menu.rs @@ -3,6 +3,7 @@ //! Simplified menu structure - settings moved to Settings panel. use eframe::egui; +use rust_i18n::t; use crate::app::UltraLogApp; use crate::state::{ActivePanel, ActiveTool, LoadingState}; @@ -21,7 +22,7 @@ impl UltraLogApp { .insert(egui::TextStyle::Button, egui::FontId::proportional(font_15)); // File menu - ui.menu_button("File", |ui| { + ui.menu_button(t!("menu.file"), |ui| { ui.set_min_width(180.0); // Increase font size for dropdown items @@ -36,8 +37,8 @@ impl UltraLogApp { // Open file option if ui - .add_enabled(!is_loading, egui::Button::new("Open Log File...")) - .on_hover_text("⌘O") + .add_enabled(!is_loading, egui::Button::new(t!("menu.open_log_file"))) + .on_hover_text("\u{2318}O") .clicked() { if let Some(path) = rfd::FileDialog::new() @@ -54,8 +55,8 @@ impl UltraLogApp { // Close current tab let has_tabs = !self.tabs.is_empty(); if ui - .add_enabled(has_tabs, egui::Button::new("Close Tab")) - .on_hover_text("⌘W") + .add_enabled(has_tabs, egui::Button::new(t!("menu.close_tab"))) + .on_hover_text("\u{2318}W") .clicked() { if let Some(tab_idx) = self.active_tab { @@ -81,22 +82,22 @@ impl UltraLogApp { let can_export = has_chart_data || has_histogram_data; ui.add_enabled_ui(can_export, |ui| { - ui.menu_button("Export", |ui| { + ui.menu_button(t!("menu.export"), |ui| { ui.style_mut() .text_styles .insert(egui::TextStyle::Button, egui::FontId::proportional(font_14)); if self.active_tool == ActiveTool::Histogram && has_histogram_data { - if ui.button("Export Histogram as PDF...").clicked() { + if ui.button(t!("menu.export_histogram_pdf")).clicked() { self.export_histogram_pdf(); ui.close(); } } else if has_chart_data { - if ui.button("Export as PNG...").clicked() { + if ui.button(t!("menu.export_png")).clicked() { self.export_chart_png(); ui.close(); } - if ui.button("Export as PDF...").clicked() { + if ui.button(t!("menu.export_pdf")).clicked() { self.export_chart_pdf(); ui.close(); } @@ -106,7 +107,7 @@ impl UltraLogApp { }); // View menu - tool modes and panels - ui.menu_button("View", |ui| { + ui.menu_button(t!("menu.view"), |ui| { ui.set_min_width(200.0); ui.style_mut() @@ -118,14 +119,18 @@ impl UltraLogApp { // Tool modes ui.label( - egui::RichText::new("Tool Mode") + egui::RichText::new(t!("menu.tool_mode")) .size(font_14) .color(egui::Color32::GRAY), ); if ui - .radio_value(&mut self.active_tool, ActiveTool::LogViewer, "Log Viewer") - .on_hover_text("⌘1") + .radio_value( + &mut self.active_tool, + ActiveTool::LogViewer, + t!("menu.log_viewer"), + ) + .on_hover_text("\u{2318}1") .clicked() { ui.close(); @@ -134,16 +139,20 @@ impl UltraLogApp { .radio_value( &mut self.active_tool, ActiveTool::ScatterPlot, - "Scatter Plots", + t!("menu.scatter_plots"), ) - .on_hover_text("⌘2") + .on_hover_text("\u{2318}2") .clicked() { ui.close(); } if ui - .radio_value(&mut self.active_tool, ActiveTool::Histogram, "Histogram") - .on_hover_text("⌘3") + .radio_value( + &mut self.active_tool, + ActiveTool::Histogram, + t!("menu.histogram"), + ) + .on_hover_text("\u{2318}3") .clicked() { ui.close(); @@ -153,35 +162,43 @@ impl UltraLogApp { // Panel navigation ui.label( - egui::RichText::new("Side Panel") + egui::RichText::new(t!("menu.side_panel")) .size(font_14) .color(egui::Color32::GRAY), ); if ui - .radio_value(&mut self.active_panel, ActivePanel::Files, "Files") - .on_hover_text("⌘⇧F") + .radio_value(&mut self.active_panel, ActivePanel::Files, t!("menu.files")) + .on_hover_text("\u{2318}\u{21E7}F") .clicked() { ui.close(); } if ui - .radio_value(&mut self.active_panel, ActivePanel::Channels, "Channels") - .on_hover_text("⌘⇧C") + .radio_value( + &mut self.active_panel, + ActivePanel::Channels, + t!("menu.channels"), + ) + .on_hover_text("\u{2318}\u{21E7}C") .clicked() { ui.close(); } if ui - .radio_value(&mut self.active_panel, ActivePanel::Tools, "Tools") - .on_hover_text("⌘⇧T") + .radio_value(&mut self.active_panel, ActivePanel::Tools, t!("menu.tools")) + .on_hover_text("\u{2318}\u{21E7}T") .clicked() { ui.close(); } if ui - .radio_value(&mut self.active_panel, ActivePanel::Settings, "Settings") - .on_hover_text("⌘,") + .radio_value( + &mut self.active_panel, + ActivePanel::Settings, + t!("menu.settings"), + ) + .on_hover_text("\u{2318},") .clicked() { ui.close(); @@ -189,7 +206,7 @@ impl UltraLogApp { }); // Help menu - ui.menu_button("Help", |ui| { + ui.menu_button(t!("menu.help"), |ui| { ui.set_min_width(200.0); ui.style_mut() @@ -199,19 +216,19 @@ impl UltraLogApp { .text_styles .insert(egui::TextStyle::Body, egui::FontId::proportional(font_14)); - if ui.button("Documentation").clicked() { + if ui.button(t!("menu.documentation")).clicked() { let _ = open::that("https://github.com/SomethingNew71/UltraLog/wiki"); ui.close(); } - if ui.button("Report Issue").clicked() { + if ui.button(t!("menu.report_issue")).clicked() { let _ = open::that("https://github.com/SomethingNew71/UltraLog/issues"); ui.close(); } ui.separator(); - if ui.button("Support Development").clicked() { + if ui.button(t!("menu.support_development")).clicked() { let _ = open::that("https://github.com/sponsors/SomethingNew71"); ui.close(); } @@ -225,9 +242,9 @@ impl UltraLogApp { | crate::updater::UpdateState::Downloading ); let button_text = if is_checking { - "Checking for Updates..." + t!("menu.checking_for_updates") } else { - "Check for Updates" + t!("menu.check_for_updates") }; if ui @@ -242,8 +259,11 @@ impl UltraLogApp { ui.horizontal(|ui| { ui.label( - egui::RichText::new(format!("Version {}", env!("CARGO_PKG_VERSION"))) - .color(egui::Color32::GRAY), + egui::RichText::new(t!( + "menu.version", + version = env!("CARGO_PKG_VERSION") + )) + .color(egui::Color32::GRAY), ); }); }); diff --git a/src/ui/normalization_editor.rs b/src/ui/normalization_editor.rs index 35ef210..3331e49 100644 --- a/src/ui/normalization_editor.rs +++ b/src/ui/normalization_editor.rs @@ -3,6 +3,7 @@ //! Provides a window for users to view and customize field name mappings. use eframe::egui; +use rust_i18n::t; use crate::app::UltraLogApp; use crate::normalize::get_builtin_mappings; @@ -16,7 +17,7 @@ impl UltraLogApp { let mut open = true; - egui::Window::new("Field Normalization Editor") + egui::Window::new(t!("normalization.title")) .open(&mut open) .resizable(true) .default_width(550.0) @@ -25,10 +26,10 @@ impl UltraLogApp { .show(ctx, |ui| { // Header with reset button ui.horizontal(|ui| { - ui.heading("Field Name Mappings"); + ui.heading(t!("normalization.field_mappings")); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { if !self.custom_normalizations.is_empty() - && ui.button("Reset to Defaults").clicked() + && ui.button(t!("normalization.reset_defaults")).clicked() { self.custom_normalizations.clear(); self.norm_editor_extend_source.clear(); @@ -43,12 +44,10 @@ impl UltraLogApp { // --- Extend Built-in Mappings Section --- ui.separator(); ui.add_space(4.0); - ui.label(egui::RichText::new("Extend Built-in Mappings").strong()); + ui.label(egui::RichText::new(t!("normalization.extend_builtin")).strong()); ui.label( - egui::RichText::new( - "Add your own source names that map to existing normalized field names.", - ) - .color(egui::Color32::GRAY), + egui::RichText::new(t!("normalization.extend_description")) + .color(egui::Color32::GRAY), ); ui.add_space(8.0); @@ -57,33 +56,35 @@ impl UltraLogApp { let builtin_names: Vec<&str> = builtin_mappings.iter().map(|(n, _)| *n).collect(); ui.horizontal(|ui| { - ui.label("Source Name:"); + ui.label(t!("normalization.source_name")); ui.add( egui::TextEdit::singleline(&mut self.norm_editor_extend_source) - .hint_text("e.g., MyCustomRPM") + .hint_text(t!("normalization.source_hint")) .desired_width(150.0), ); ui.label("→"); - ui.label("Maps To:"); + ui.label(t!("normalization.maps_to")); // Dropdown for selecting existing normalized name + let default_select = t!("normalization.select"); let selected_text = self .norm_editor_selected_target .as_deref() - .unwrap_or("Select..."); + .unwrap_or(&default_select); egui::ComboBox::from_id_salt("extend_builtin_combo") .selected_text(selected_text) .width(120.0) .show_ui(ui, |ui| { for name in &builtin_names { - let is_selected = self.norm_editor_selected_target.as_deref() == Some(*name); + let is_selected = + self.norm_editor_selected_target.as_deref() == Some(*name); if ui.selectable_label(is_selected, *name).clicked() { self.norm_editor_selected_target = Some(name.to_string()); } } }); - if ui.button("Add").clicked() + if ui.button(t!("common.add")).clicked() && !self.norm_editor_extend_source.is_empty() { if let Some(target) = &self.norm_editor_selected_target { @@ -101,30 +102,28 @@ impl UltraLogApp { // --- Custom Mappings Section --- ui.separator(); ui.add_space(4.0); - ui.label(egui::RichText::new("Create New Mapping").strong()); + ui.label(egui::RichText::new(t!("normalization.create_new")).strong()); ui.label( - egui::RichText::new( - "Define completely new normalized names for your custom channels.", - ) - .color(egui::Color32::GRAY), + egui::RichText::new(t!("normalization.create_description")) + .color(egui::Color32::GRAY), ); ui.add_space(8.0); ui.horizontal(|ui| { - ui.label("Source Name:"); + ui.label(t!("normalization.source_name")); ui.add( egui::TextEdit::singleline(&mut self.norm_editor_custom_source) - .hint_text("e.g., Sensor_XYZ") + .hint_text(t!("normalization.custom_source_hint")) .desired_width(150.0), ); ui.label("→"); - ui.label("Display As:"); + ui.label(t!("normalization.display_as")); ui.add( egui::TextEdit::singleline(&mut self.norm_editor_custom_target) - .hint_text("e.g., Custom Sensor") + .hint_text(t!("normalization.custom_target_hint")) .desired_width(150.0), ); - if ui.button("Add").clicked() + if ui.button(t!("common.add")).clicked() && !self.norm_editor_custom_source.is_empty() && !self.norm_editor_custom_target.is_empty() { @@ -144,7 +143,7 @@ impl UltraLogApp { ui.separator(); ui.add_space(4.0); ui.horizontal(|ui| { - ui.label(egui::RichText::new("Your Custom Mappings").strong()); + ui.label(egui::RichText::new(t!("normalization.your_mappings")).strong()); ui.label( egui::RichText::new(format!("({})", self.custom_normalizations.len())) .color(egui::Color32::GRAY), @@ -164,13 +163,19 @@ impl UltraLogApp { .min_col_width(100.0) .spacing([16.0, 8.0]) .show(ui, |ui| { - ui.label(egui::RichText::new("Source").strong()); - ui.label(egui::RichText::new("Display As").strong()); + ui.label( + egui::RichText::new(t!("normalization.source")).strong(), + ); + ui.label( + egui::RichText::new(t!("normalization.display_as")) + .strong(), + ); ui.label(""); ui.end_row(); // Sort by target name for better organization - let mut sorted: Vec<_> = self.custom_normalizations.iter().collect(); + let mut sorted: Vec<_> = + self.custom_normalizations.iter().collect(); sorted.sort_by(|a, b| a.1.cmp(b.1)); for (source, target) in sorted { @@ -179,7 +184,7 @@ impl UltraLogApp { egui::RichText::new(target) .color(egui::Color32::LIGHT_BLUE), ); - if ui.small_button("Remove").clicked() { + if ui.small_button(t!("common.remove")).clicked() { to_remove = Some(source.clone()); } ui.end_row(); @@ -196,14 +201,12 @@ impl UltraLogApp { ui.separator(); // Built-in mappings reference (collapsible) - egui::CollapsingHeader::new("Built-in Mappings Reference") + egui::CollapsingHeader::new(t!("normalization.builtin_reference")) .default_open(false) .show(ui, |ui| { ui.label( - egui::RichText::new( - "These are the default mappings. You can extend them by adding new source names above.", - ) - .color(egui::Color32::GRAY), + egui::RichText::new(t!("normalization.builtin_description")) + .color(egui::Color32::GRAY), ); ui.add_space(8.0); diff --git a/src/ui/scatter_plot.rs b/src/ui/scatter_plot.rs index 0e425c2..97076d2 100644 --- a/src/ui/scatter_plot.rs +++ b/src/ui/scatter_plot.rs @@ -4,6 +4,7 @@ //! relationships between channels as a 2D histogram with hit count coloring. use eframe::egui; +use rust_i18n::t; use crate::app::UltraLogApp; use crate::normalize::{normalize_channel_name_with_custom, sort_channels_by_priority}; @@ -44,7 +45,7 @@ impl UltraLogApp { if self.active_tab.is_none() || self.files.is_empty() { ui.centered_and_justified(|ui| { ui.label( - egui::RichText::new("Load a log file to use scatter plots") + egui::RichText::new(t!("scatter.no_file_loaded")) .size(self.scaled_font(20.0)) .color(egui::Color32::GRAY), ); @@ -193,7 +194,7 @@ impl UltraLogApp { ui.horizontal(|ui| { // X Axis selector - ui.label("X Axis:"); + ui.label(t!("scatter.x_axis")); egui::ComboBox::from_id_salt(if is_left { "left_x" } else { "right_x" }) .selected_text( current_x @@ -212,7 +213,7 @@ impl UltraLogApp { ui.add_space(16.0); // Y Axis selector - ui.label("Y Axis:"); + ui.label(t!("scatter.y_axis")); egui::ComboBox::from_id_salt(if is_left { "left_y" } else { "right_y" }) .selected_text( current_y @@ -231,8 +232,11 @@ impl UltraLogApp { ui.add_space(16.0); // Z Axis is always "Hits" (density) - ui.label("Z Axis:"); - ui.label(egui::RichText::new("Hits").color(egui::Color32::from_rgb(150, 150, 150))); + ui.label(t!("scatter.z_axis")); + ui.label( + egui::RichText::new(t!("scatter.hits")) + .color(egui::Color32::from_rgb(150, 150, 150)), + ); }); // Apply channel updates after UI is rendered @@ -639,7 +643,7 @@ impl UltraLogApp { .show(ui, |ui| { ui.horizontal(|ui| { ui.label( - egui::RichText::new("Hits:") + egui::RichText::new(t!("scatter.hits_label")) .size(font_11) .color(egui::Color32::WHITE), ); diff --git a/src/ui/settings_panel.rs b/src/ui/settings_panel.rs index 8d8fbc5..3c46b83 100644 --- a/src/ui/settings_panel.rs +++ b/src/ui/settings_panel.rs @@ -3,9 +3,11 @@ //! This panel provides a single location for all user preferences. use eframe::egui; +use rust_i18n::t; use crate::analytics; use crate::app::UltraLogApp; +use crate::i18n::Language; use crate::state::FontScale; use crate::units::{ AccelerationUnit, DistanceUnit, FlowUnit, FuelEconomyUnit, PressureUnit, SpeedUnit, @@ -16,6 +18,13 @@ use crate::updater::UpdateState; impl UltraLogApp { /// Render the settings panel content (called from side_panel.rs) pub fn render_settings_panel_content(&mut self, ui: &mut egui::Ui) { + // Language settings section + self.render_language_settings(ui); + + ui.add_space(8.0); + ui.separator(); + ui.add_space(8.0); + // Display settings section self.render_display_settings(ui); @@ -41,66 +50,120 @@ impl UltraLogApp { self.render_update_settings(ui); } + /// Render language settings section + fn render_language_settings(&mut self, ui: &mut egui::Ui) { + let font_12 = self.scaled_font(12.0); + let font_14 = self.scaled_font(14.0); + + egui::CollapsingHeader::new( + egui::RichText::new(format!("\u{1F310} {}", t!("settings.language"))) + .size(font_14) + .strong(), + ) + .default_open(true) + .show(ui, |ui| { + ui.label( + egui::RichText::new(t!("settings.language_desc")) + .size(font_12) + .color(egui::Color32::GRAY), + ); + + ui.add_space(8.0); + + egui::ComboBox::from_id_salt("language_selector") + .selected_text(self.language.display_name()) + .width(140.0) + .show_ui(ui, |ui| { + for lang in Language::all() { + if ui + .selectable_value(&mut self.language, *lang, lang.display_name()) + .changed() + { + // Update locale immediately + rust_i18n::set_locale(self.language.locale_code()); + + // Save to persistent settings + self.user_settings.language = self.language; + if let Err(e) = self.user_settings.save() { + self.show_toast_error(&format!( + "{}: {}", + t!("toast.library_save_failed", error = e), + e + )); + } + + // Request repaint to refresh all UI text + ui.ctx().request_repaint(); + } + } + }); + }); + } + /// Render display settings section fn render_display_settings(&mut self, ui: &mut egui::Ui) { let font_12 = self.scaled_font(12.0); let font_14 = self.scaled_font(14.0); - egui::CollapsingHeader::new(egui::RichText::new("🖥 Display").size(font_14).strong()) - .default_open(true) - .show(ui, |ui| { - // Colorblind mode - let old_color_blind_mode = self.color_blind_mode; - ui.checkbox( - &mut self.color_blind_mode, - egui::RichText::new("Color Blind Mode").size(font_14), - ); - if self.color_blind_mode != old_color_blind_mode { - analytics::track_colorblind_mode_toggled(self.color_blind_mode); - } - ui.label( - egui::RichText::new("Use accessible color palette (Wong's palette)") - .size(font_12) - .color(egui::Color32::GRAY), - ); - - ui.add_space(8.0); + egui::CollapsingHeader::new( + egui::RichText::new(format!("\u{1F5A5} {}", t!("settings.display"))) + .size(font_14) + .strong(), + ) + .default_open(true) + .show(ui, |ui| { + // Colorblind mode + let old_color_blind_mode = self.color_blind_mode; + ui.checkbox( + &mut self.color_blind_mode, + egui::RichText::new(t!("settings.color_blind_mode")).size(font_14), + ); + if self.color_blind_mode != old_color_blind_mode { + analytics::track_colorblind_mode_toggled(self.color_blind_mode); + } + ui.label( + egui::RichText::new(t!("settings.color_blind_desc")) + .size(font_12) + .color(egui::Color32::GRAY), + ); + + ui.add_space(8.0); + + // Font size + ui.label(egui::RichText::new(t!("settings.font_size")).size(font_14)); + ui.horizontal(|ui| { + ui.selectable_value(&mut self.font_scale, FontScale::Small, "S"); + ui.selectable_value(&mut self.font_scale, FontScale::Medium, "M"); + ui.selectable_value(&mut self.font_scale, FontScale::Large, "L"); + ui.selectable_value(&mut self.font_scale, FontScale::ExtraLarge, "XL"); + }); - // Font size - ui.label(egui::RichText::new("Font Size:").size(font_14)); + ui.add_space(8.0); + + // Cursor tracking + ui.checkbox( + &mut self.cursor_tracking, + egui::RichText::new(t!("settings.cursor_tracking")).size(font_14), + ); + ui.label( + egui::RichText::new(t!("settings.cursor_tracking_desc")) + .size(font_12) + .color(egui::Color32::GRAY), + ); + + if self.cursor_tracking { + ui.add_space(4.0); ui.horizontal(|ui| { - ui.selectable_value(&mut self.font_scale, FontScale::Small, "S"); - ui.selectable_value(&mut self.font_scale, FontScale::Medium, "M"); - ui.selectable_value(&mut self.font_scale, FontScale::Large, "L"); - ui.selectable_value(&mut self.font_scale, FontScale::ExtraLarge, "XL"); + ui.label(egui::RichText::new(t!("settings.window")).size(font_12)); + ui.add( + egui::Slider::new(&mut self.view_window_seconds, 5.0..=120.0) + .suffix("s") + .logarithmic(true) + .text(""), + ); }); - - ui.add_space(8.0); - - // Cursor tracking - ui.checkbox( - &mut self.cursor_tracking, - egui::RichText::new("Cursor Tracking").size(font_14), - ); - ui.label( - egui::RichText::new("Keep cursor centered while scrubbing") - .size(font_12) - .color(egui::Color32::GRAY), - ); - - if self.cursor_tracking { - ui.add_space(4.0); - ui.horizontal(|ui| { - ui.label(egui::RichText::new("Window:").size(font_12)); - ui.add( - egui::Slider::new(&mut self.view_window_seconds, 5.0..=120.0) - .suffix("s") - .logarithmic(true) - .text(""), - ); - }); - } - }); + } + }); } /// Render field normalization settings @@ -108,52 +171,56 @@ impl UltraLogApp { let font_12 = self.scaled_font(12.0); let font_14 = self.scaled_font(14.0); - egui::CollapsingHeader::new(egui::RichText::new("📝 Field Names").size(font_14).strong()) - .default_open(true) - .show(ui, |ui| { - ui.checkbox( - &mut self.field_normalization, - egui::RichText::new("Field Normalization").size(font_14), - ); + egui::CollapsingHeader::new( + egui::RichText::new(format!("\u{1F4DD} {}", t!("settings.field_names"))) + .size(font_14) + .strong(), + ) + .default_open(true) + .show(ui, |ui| { + ui.checkbox( + &mut self.field_normalization, + egui::RichText::new(t!("settings.field_normalization")).size(font_14), + ); + ui.label( + egui::RichText::new(t!("settings.field_normalization_desc")) + .size(font_12) + .color(egui::Color32::GRAY), + ); + + ui.add_space(8.0); + + // Custom mappings count + let custom_count = self.custom_normalizations.len(); + if custom_count > 0 { ui.label( - egui::RichText::new("Standardize channel names across ECU types") + egui::RichText::new(t!("settings.custom_mappings", count = custom_count)) .size(font_12) .color(egui::Color32::GRAY), ); - - ui.add_space(8.0); - - // Custom mappings count - let custom_count = self.custom_normalizations.len(); - if custom_count > 0 { + } + + // Edit mappings button + let btn = egui::Frame::NONE + .fill(egui::Color32::from_rgb(60, 60, 60)) + .corner_radius(4) + .inner_margin(egui::vec2(12.0, 6.0)) + .show(ui, |ui| { ui.label( - egui::RichText::new(format!("{} custom mappings", custom_count)) - .size(font_12) - .color(egui::Color32::GRAY), + egui::RichText::new(t!("settings.edit_custom_mappings")) + .color(egui::Color32::LIGHT_GRAY) + .size(font_14), ); - } - - // Edit mappings button - let btn = egui::Frame::NONE - .fill(egui::Color32::from_rgb(60, 60, 60)) - .corner_radius(4) - .inner_margin(egui::vec2(12.0, 6.0)) - .show(ui, |ui| { - ui.label( - egui::RichText::new("Edit Custom Mappings") - .color(egui::Color32::LIGHT_GRAY) - .size(font_14), - ); - }); + }); - if btn.response.interact(egui::Sense::click()).clicked() { - self.show_normalization_editor = true; - } + if btn.response.interact(egui::Sense::click()).clicked() { + self.show_normalization_editor = true; + } - if btn.response.hovered() { - ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand); - } - }); + if btn.response.hovered() { + ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand); + } + }); } /// Render unit preferences @@ -161,189 +228,193 @@ impl UltraLogApp { let font_12 = self.scaled_font(12.0); let font_14 = self.scaled_font(14.0); - egui::CollapsingHeader::new(egui::RichText::new("📐 Units").size(font_14).strong()) - .default_open(true) - .show(ui, |ui| { - ui.label( - egui::RichText::new("Select preferred units for display") - .size(font_12) - .color(egui::Color32::GRAY), - ); - ui.add_space(8.0); - - // Create a grid for unit selections - egui::Grid::new("unit_settings_grid") - .num_columns(2) - .spacing([8.0, 6.0]) - .show(ui, |ui| { - // Temperature - ui.label(egui::RichText::new("Temperature:").size(font_12)); - egui::ComboBox::from_id_salt("temp_unit") - .selected_text(self.unit_preferences.temperature.symbol()) - .width(80.0) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut self.unit_preferences.temperature, - TemperatureUnit::Celsius, - "°C", - ); - ui.selectable_value( - &mut self.unit_preferences.temperature, - TemperatureUnit::Fahrenheit, - "°F", - ); - ui.selectable_value( - &mut self.unit_preferences.temperature, - TemperatureUnit::Kelvin, - "K", - ); - }); - ui.end_row(); - - // Pressure - ui.label(egui::RichText::new("Pressure:").size(font_12)); - egui::ComboBox::from_id_salt("pressure_unit") - .selected_text(self.unit_preferences.pressure.symbol()) - .width(80.0) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut self.unit_preferences.pressure, - PressureUnit::KPa, - "kPa", - ); - ui.selectable_value( - &mut self.unit_preferences.pressure, - PressureUnit::PSI, - "psi", - ); - ui.selectable_value( - &mut self.unit_preferences.pressure, - PressureUnit::Bar, - "bar", - ); - }); - ui.end_row(); - - // Speed - ui.label(egui::RichText::new("Speed:").size(font_12)); - egui::ComboBox::from_id_salt("speed_unit") - .selected_text(self.unit_preferences.speed.symbol()) - .width(80.0) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut self.unit_preferences.speed, - SpeedUnit::KmH, - "km/h", - ); - ui.selectable_value( - &mut self.unit_preferences.speed, - SpeedUnit::Mph, - "mph", - ); - }); - ui.end_row(); - - // Distance - ui.label(egui::RichText::new("Distance:").size(font_12)); - egui::ComboBox::from_id_salt("distance_unit") - .selected_text(self.unit_preferences.distance.symbol()) - .width(80.0) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut self.unit_preferences.distance, - DistanceUnit::Kilometers, - "km", - ); - ui.selectable_value( - &mut self.unit_preferences.distance, - DistanceUnit::Miles, - "mi", - ); - }); - ui.end_row(); - - // Fuel Economy - ui.label(egui::RichText::new("Fuel Economy:").size(font_12)); - egui::ComboBox::from_id_salt("fuel_unit") - .selected_text(self.unit_preferences.fuel_economy.symbol()) - .width(80.0) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut self.unit_preferences.fuel_economy, - FuelEconomyUnit::LPer100Km, - "L/100km", - ); - ui.selectable_value( - &mut self.unit_preferences.fuel_economy, - FuelEconomyUnit::Mpg, - "mpg", - ); - ui.selectable_value( - &mut self.unit_preferences.fuel_economy, - FuelEconomyUnit::KmPerL, - "km/L", - ); - }); - ui.end_row(); - - // Volume - ui.label(egui::RichText::new("Volume:").size(font_12)); - egui::ComboBox::from_id_salt("volume_unit") - .selected_text(self.unit_preferences.volume.symbol()) - .width(80.0) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut self.unit_preferences.volume, - VolumeUnit::Liters, - "L", - ); - ui.selectable_value( - &mut self.unit_preferences.volume, - VolumeUnit::Gallons, - "gal", - ); - }); - ui.end_row(); - - // Flow Rate - ui.label(egui::RichText::new("Flow Rate:").size(font_12)); - egui::ComboBox::from_id_salt("flow_unit") - .selected_text(self.unit_preferences.flow.symbol()) - .width(80.0) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut self.unit_preferences.flow, - FlowUnit::CcPerMin, - "cc/min", - ); - ui.selectable_value( - &mut self.unit_preferences.flow, - FlowUnit::LbPerHr, - "lb/hr", - ); - }); - ui.end_row(); - - // Acceleration - ui.label(egui::RichText::new("Acceleration:").size(font_12)); - egui::ComboBox::from_id_salt("accel_unit") - .selected_text(self.unit_preferences.acceleration.symbol()) - .width(80.0) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut self.unit_preferences.acceleration, - AccelerationUnit::MPerS2, - "m/s²", - ); - ui.selectable_value( - &mut self.unit_preferences.acceleration, - AccelerationUnit::G, - "g", - ); - }); - ui.end_row(); - }); - }); + egui::CollapsingHeader::new( + egui::RichText::new(format!("\u{1F4D0} {}", t!("settings.units"))) + .size(font_14) + .strong(), + ) + .default_open(true) + .show(ui, |ui| { + ui.label( + egui::RichText::new(t!("settings.units_desc")) + .size(font_12) + .color(egui::Color32::GRAY), + ); + ui.add_space(8.0); + + // Create a grid for unit selections + egui::Grid::new("unit_settings_grid") + .num_columns(2) + .spacing([8.0, 6.0]) + .show(ui, |ui| { + // Temperature + ui.label(egui::RichText::new(t!("settings.temperature")).size(font_12)); + egui::ComboBox::from_id_salt("temp_unit") + .selected_text(self.unit_preferences.temperature.symbol()) + .width(80.0) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut self.unit_preferences.temperature, + TemperatureUnit::Celsius, + "°C", + ); + ui.selectable_value( + &mut self.unit_preferences.temperature, + TemperatureUnit::Fahrenheit, + "°F", + ); + ui.selectable_value( + &mut self.unit_preferences.temperature, + TemperatureUnit::Kelvin, + "K", + ); + }); + ui.end_row(); + + // Pressure + ui.label(egui::RichText::new(t!("settings.pressure")).size(font_12)); + egui::ComboBox::from_id_salt("pressure_unit") + .selected_text(self.unit_preferences.pressure.symbol()) + .width(80.0) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut self.unit_preferences.pressure, + PressureUnit::KPa, + "kPa", + ); + ui.selectable_value( + &mut self.unit_preferences.pressure, + PressureUnit::PSI, + "psi", + ); + ui.selectable_value( + &mut self.unit_preferences.pressure, + PressureUnit::Bar, + "bar", + ); + }); + ui.end_row(); + + // Speed + ui.label(egui::RichText::new(t!("settings.speed")).size(font_12)); + egui::ComboBox::from_id_salt("speed_unit") + .selected_text(self.unit_preferences.speed.symbol()) + .width(80.0) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut self.unit_preferences.speed, + SpeedUnit::KmH, + "km/h", + ); + ui.selectable_value( + &mut self.unit_preferences.speed, + SpeedUnit::Mph, + "mph", + ); + }); + ui.end_row(); + + // Distance + ui.label(egui::RichText::new(t!("settings.distance")).size(font_12)); + egui::ComboBox::from_id_salt("distance_unit") + .selected_text(self.unit_preferences.distance.symbol()) + .width(80.0) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut self.unit_preferences.distance, + DistanceUnit::Kilometers, + "km", + ); + ui.selectable_value( + &mut self.unit_preferences.distance, + DistanceUnit::Miles, + "mi", + ); + }); + ui.end_row(); + + // Fuel Economy + ui.label(egui::RichText::new(t!("settings.fuel_economy")).size(font_12)); + egui::ComboBox::from_id_salt("fuel_unit") + .selected_text(self.unit_preferences.fuel_economy.symbol()) + .width(80.0) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut self.unit_preferences.fuel_economy, + FuelEconomyUnit::LPer100Km, + "L/100km", + ); + ui.selectable_value( + &mut self.unit_preferences.fuel_economy, + FuelEconomyUnit::Mpg, + "mpg", + ); + ui.selectable_value( + &mut self.unit_preferences.fuel_economy, + FuelEconomyUnit::KmPerL, + "km/L", + ); + }); + ui.end_row(); + + // Volume + ui.label(egui::RichText::new(t!("settings.volume")).size(font_12)); + egui::ComboBox::from_id_salt("volume_unit") + .selected_text(self.unit_preferences.volume.symbol()) + .width(80.0) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut self.unit_preferences.volume, + VolumeUnit::Liters, + "L", + ); + ui.selectable_value( + &mut self.unit_preferences.volume, + VolumeUnit::Gallons, + "gal", + ); + }); + ui.end_row(); + + // Flow Rate + ui.label(egui::RichText::new(t!("settings.flow_rate")).size(font_12)); + egui::ComboBox::from_id_salt("flow_unit") + .selected_text(self.unit_preferences.flow.symbol()) + .width(80.0) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut self.unit_preferences.flow, + FlowUnit::CcPerMin, + "cc/min", + ); + ui.selectable_value( + &mut self.unit_preferences.flow, + FlowUnit::LbPerHr, + "lb/hr", + ); + }); + ui.end_row(); + + // Acceleration + ui.label(egui::RichText::new(t!("settings.acceleration")).size(font_12)); + egui::ComboBox::from_id_salt("accel_unit") + .selected_text(self.unit_preferences.acceleration.symbol()) + .width(80.0) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut self.unit_preferences.acceleration, + AccelerationUnit::MPerS2, + "m/s²", + ); + ui.selectable_value( + &mut self.unit_preferences.acceleration, + AccelerationUnit::G, + "g", + ); + }); + ui.end_row(); + }); + }); } /// Render update settings @@ -351,110 +422,117 @@ impl UltraLogApp { let font_12 = self.scaled_font(12.0); let font_14 = self.scaled_font(14.0); - egui::CollapsingHeader::new(egui::RichText::new("🔄 Updates").size(font_14).strong()) - .default_open(true) - .show(ui, |ui| { - // Auto-check preference - ui.checkbox( - &mut self.auto_check_updates, - egui::RichText::new("Check on startup").size(font_14), - ); - ui.label( - egui::RichText::new("Automatically check for new versions") - .size(font_12) - .color(egui::Color32::GRAY), - ); + egui::CollapsingHeader::new( + egui::RichText::new(format!("\u{1F504} {}", t!("settings.updates"))) + .size(font_14) + .strong(), + ) + .default_open(true) + .show(ui, |ui| { + // Auto-check preference + ui.checkbox( + &mut self.auto_check_updates, + egui::RichText::new(t!("settings.check_on_startup")).size(font_14), + ); + ui.label( + egui::RichText::new(t!("settings.auto_check_desc")) + .size(font_12) + .color(egui::Color32::GRAY), + ); + + ui.add_space(8.0); + + // Check now button + let is_checking = matches!(self.update_state, UpdateState::Checking); + ui.add_enabled_ui(!is_checking, |ui| { + let btn = egui::Frame::NONE + .fill(egui::Color32::from_rgb(60, 60, 60)) + .corner_radius(4) + .inner_margin(egui::vec2(12.0, 6.0)) + .show(ui, |ui| { + let text = if is_checking { + t!("settings.checking") + } else { + t!("settings.check_for_updates") + }; + ui.label( + egui::RichText::new(text) + .color(egui::Color32::LIGHT_GRAY) + .size(font_14), + ); + }); - ui.add_space(8.0); - - // Check now button - let is_checking = matches!(self.update_state, UpdateState::Checking); - ui.add_enabled_ui(!is_checking, |ui| { - let btn = egui::Frame::NONE - .fill(egui::Color32::from_rgb(60, 60, 60)) - .corner_radius(4) - .inner_margin(egui::vec2(12.0, 6.0)) - .show(ui, |ui| { - let text = if is_checking { - "Checking..." - } else { - "Check for Updates" - }; - ui.label( - egui::RichText::new(text) - .color(egui::Color32::LIGHT_GRAY) - .size(font_14), - ); - }); + if !is_checking && btn.response.interact(egui::Sense::click()).clicked() { + self.start_update_check(); + } - if !is_checking && btn.response.interact(egui::Sense::click()).clicked() { - self.start_update_check(); - } + if btn.response.hovered() && !is_checking { + ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand); + } + }); - if btn.response.hovered() && !is_checking { - ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand); - } - }); + ui.add_space(8.0); - ui.add_space(8.0); + // Version info + let version = env!("CARGO_PKG_VERSION"); + ui.label( + egui::RichText::new(t!("settings.current_version", version = version)) + .size(font_12) + .color(egui::Color32::GRAY), + ); - // Version info - let version = env!("CARGO_PKG_VERSION"); - ui.label( - egui::RichText::new(format!("Current version: {}", version)) + // Show update status + match &self.update_state { + UpdateState::Checking => { + ui.horizontal(|ui| { + ui.spinner(); + ui.label( + egui::RichText::new(t!("settings.checking")) + .size(font_12) + .color(egui::Color32::GRAY), + ); + }); + } + UpdateState::UpdateAvailable(info) => { + ui.add_space(4.0); + ui.label( + egui::RichText::new(format!( + "\u{2713} {}", + t!("settings.update_available", version = &info.new_version) + )) .size(font_12) - .color(egui::Color32::GRAY), - ); + .color(egui::Color32::from_rgb(150, 200, 150)), + ); - // Show update status - match &self.update_state { - UpdateState::Checking => { - ui.horizontal(|ui| { - ui.spinner(); - ui.label( - egui::RichText::new("Checking...") - .size(font_12) - .color(egui::Color32::GRAY), - ); - }); - } - UpdateState::UpdateAvailable(info) => { - ui.add_space(4.0); - ui.label( + let view_btn = ui.add( + egui::Label::new( egui::RichText::new(format!( - "✓ Update available: v{}", - info.new_version + "{} \u{2192}", + t!("settings.view_details") )) .size(font_12) - .color(egui::Color32::from_rgb(150, 200, 150)), - ); - - let view_btn = ui.add( - egui::Label::new( - egui::RichText::new("View Details →") - .size(font_12) - .color(egui::Color32::from_rgb(150, 180, 220)), - ) - .sense(egui::Sense::click()), - ); - - if view_btn.clicked() { - self.show_update_dialog = true; - } + .color(egui::Color32::from_rgb(150, 180, 220)), + ) + .sense(egui::Sense::click()), + ); - if view_btn.hovered() { - ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand); - } + if view_btn.clicked() { + self.show_update_dialog = true; } - UpdateState::Error(msg) => { - ui.label( - egui::RichText::new(format!("⚠ {}", msg)) - .size(font_12) - .color(egui::Color32::from_rgb(200, 150, 100)), - ); + + if view_btn.hovered() { + ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand); } - _ => {} } - }); + UpdateState::Error(msg) => { + ui.label( + egui::RichText::new(format!("⚠ {}", msg)) + .size(font_12) + .color(egui::Color32::from_rgb(200, 150, 100)), + ); + } + _ => {} + } + }); } } diff --git a/src/ui/timeline.rs b/src/ui/timeline.rs index dae566e..34581f0 100644 --- a/src/ui/timeline.rs +++ b/src/ui/timeline.rs @@ -1,6 +1,7 @@ //! Timeline scrubber and playback controls UI. use eframe::egui; +use rust_i18n::t; use crate::analytics; use crate::app::UltraLogApp; @@ -77,7 +78,11 @@ impl UltraLogApp { let button_size = egui::vec2(28.0, 28.0); // Play/Pause button - let play_text = if self.is_playing { "⏸" } else { "▶" }; + let play_text = if self.is_playing { + "\u{23F8}" + } else { + "\u{25B6}" + }; let play_button = egui::Button::new( egui::RichText::new(play_text) .size(self.scaled_font(16.0)) @@ -109,7 +114,7 @@ impl UltraLogApp { // Stop button (resets to beginning) let stop_button = egui::Button::new( - egui::RichText::new("⏹") + egui::RichText::new("\u{23F9}") .size(self.scaled_font(16.0)) .color(egui::Color32::from_rgb(191, 78, 48)), // Rust orange ) @@ -130,7 +135,7 @@ impl UltraLogApp { // Playback speed selector ui.label( - egui::RichText::new("Speed:") + egui::RichText::new(t!("timeline.speed")) .color(egui::Color32::GRAY) .size(font_14), ); @@ -150,7 +155,7 @@ impl UltraLogApp { // Current time display if let Some(time) = self.get_cursor_time() { ui.label( - egui::RichText::new(format!("Time: {}", Self::format_time(time))) + egui::RichText::new(t!("timeline.time", time = Self::format_time(time))) .strong() .color(egui::Color32::from_rgb(0, 255, 255)) // Cyan to match cursor .size(font_14), @@ -166,10 +171,10 @@ impl UltraLogApp { if file_index < self.files.len() { let total_records = self.files[file_index].log.data.len(); ui.label( - egui::RichText::new(format!( - "Record {} of {}", - record + 1, - total_records + egui::RichText::new(t!( + "timeline.record", + current = record + 1, + total = total_records )) .color(egui::Color32::LIGHT_GRAY) .size(font_14), diff --git a/src/ui/tool_switcher.rs b/src/ui/tool_switcher.rs index b036b55..2ac0ca8 100644 --- a/src/ui/tool_switcher.rs +++ b/src/ui/tool_switcher.rs @@ -4,6 +4,7 @@ //! allowing users to switch between Log Viewer and Scatter Plots views. use eframe::egui; +use rust_i18n::t; use crate::analytics; use crate::app::UltraLogApp; @@ -44,10 +45,17 @@ impl UltraLogApp { egui::Stroke::new(1.0, egui::Color32::from_rgb(80, 80, 80)) }; + // Get translated tool name + let tool_name = match tool { + ActiveTool::LogViewer => t!("tools.log_viewer"), + ActiveTool::ScatterPlot => t!("tools.scatter_plots"), + ActiveTool::Histogram => t!("tools.histogram"), + }; + // Create pill-style button let response = ui.add( egui::Button::new( - egui::RichText::new(tool.name()) + egui::RichText::new(tool_name.as_ref()) .size(self.scaled_font(14.0)) .color(text_color), ) diff --git a/src/ui/update_dialog.rs b/src/ui/update_dialog.rs index 4d14faa..a2d1b49 100644 --- a/src/ui/update_dialog.rs +++ b/src/ui/update_dialog.rs @@ -3,6 +3,7 @@ //! Displays update information and provides download/install options. use eframe::egui; +use rust_i18n::t; use crate::app::UltraLogApp; use crate::updater::{InstallResult, UpdateInfo, UpdateState}; @@ -17,7 +18,7 @@ impl UltraLogApp { let mut open = true; let mut should_close = false; - egui::Window::new("Update Available") + egui::Window::new(t!("update.update_available")) .open(&mut open) .resizable(false) .collapsible(false) @@ -62,7 +63,7 @@ impl UltraLogApp { ui.add_space(10.0); ui.label( - egui::RichText::new("A new version is available!") + egui::RichText::new(t!("update.new_version_available")) .size(self.scaled_font(18.0)) .strong(), ); @@ -71,12 +72,12 @@ impl UltraLogApp { // Version comparison ui.horizontal(|ui| { - ui.label("Current version:"); + ui.label(t!("update.current_version")); ui.label(egui::RichText::new(&info.current_version).color(egui::Color32::GRAY)); }); ui.horizontal(|ui| { - ui.label("New version:"); + ui.label(t!("update.new_version")); ui.label( egui::RichText::new(&info.new_version) .color(egui::Color32::LIGHT_GREEN) @@ -89,7 +90,7 @@ impl UltraLogApp { // Download size let size_mb = info.download_size as f64 / (1024.0 * 1024.0); ui.label( - egui::RichText::new(format!("Download size: {:.1} MB", size_mb)) + egui::RichText::new(t!("update.download_size", size = format!("{:.1}", size_mb))) .color(egui::Color32::GRAY), ); @@ -97,7 +98,7 @@ impl UltraLogApp { // Release notes (scrollable, collapsible) if let Some(notes) = &info.release_notes { - egui::CollapsingHeader::new("Release Notes") + egui::CollapsingHeader::new(t!("update.release_notes")) .default_open(true) .show(ui, |ui| { egui::ScrollArea::vertical() @@ -112,15 +113,15 @@ impl UltraLogApp { // Action buttons ui.horizontal(|ui| { - if ui.button("Download & Install").clicked() { + if ui.button(t!("update.download_install")).clicked() { self.start_update_download(info.download_url.clone()); } - if ui.button("View on GitHub").clicked() { + if ui.button(t!("update.view_on_github")).clicked() { let _ = open::that(&info.release_page_url); } - if ui.button("Later").clicked() { + if ui.button(t!("update.later")).clicked() { *should_close = true; } }); @@ -133,7 +134,7 @@ impl UltraLogApp { ui.vertical_centered(|ui| { ui.add_space(20.0); - ui.label(egui::RichText::new("Downloading update...").size(self.scaled_font(16.0))); + ui.label(egui::RichText::new(t!("update.downloading")).size(self.scaled_font(16.0))); ui.add_space(15.0); @@ -141,7 +142,7 @@ impl UltraLogApp { ui.add_space(15.0); - ui.label(egui::RichText::new("Please wait...").color(egui::Color32::GRAY)); + ui.label(egui::RichText::new(t!("update.please_wait")).color(egui::Color32::GRAY)); ui.add_space(20.0); }); @@ -157,37 +158,28 @@ impl UltraLogApp { ui.add_space(20.0); ui.label( - egui::RichText::new("Download complete!") + egui::RichText::new(t!("update.download_complete")) .size(self.scaled_font(16.0)) .color(egui::Color32::LIGHT_GREEN), ); ui.add_space(15.0); - ui.label("Click Install to apply the update."); + ui.label(t!("update.install_instructions")); #[cfg(target_os = "windows")] - ui.label( - egui::RichText::new("The application will close and restart automatically.") - .color(egui::Color32::GRAY), - ); + ui.label(egui::RichText::new(t!("update.windows_restart")).color(egui::Color32::GRAY)); #[cfg(target_os = "macos")] - ui.label( - egui::RichText::new("The DMG will open - drag UltraLog to Applications.") - .color(egui::Color32::GRAY), - ); + ui.label(egui::RichText::new(t!("update.macos_dmg")).color(egui::Color32::GRAY)); #[cfg(target_os = "linux")] - ui.label( - egui::RichText::new("The application will close and restart automatically.") - .color(egui::Color32::GRAY), - ); + ui.label(egui::RichText::new(t!("update.linux_restart")).color(egui::Color32::GRAY)); ui.add_space(15.0); ui.horizontal(|ui| { - if ui.button("Install Now").clicked() { + if ui.button(t!("update.install_now")).clicked() { match crate::updater::install_update(path) { InstallResult::ReadyToRestart { message } => { self.show_toast_success(&message); @@ -207,8 +199,8 @@ impl UltraLogApp { } } - if ui.button("Install Later").clicked() { - self.show_toast("Update saved to your temp folder."); + if ui.button(t!("update.install_later")).clicked() { + self.show_toast(&t!("update.update_saved")); *should_close = true; self.update_state = UpdateState::Idle; } @@ -223,7 +215,7 @@ impl UltraLogApp { ui.add_space(20.0); ui.label( - egui::RichText::new("Update Error") + egui::RichText::new(t!("update.update_error")) .size(self.scaled_font(16.0)) .color(egui::Color32::from_rgb(191, 78, 48)), ); @@ -234,7 +226,7 @@ impl UltraLogApp { ui.add_space(15.0); - if ui.button("Close").clicked() { + if ui.button(t!("update.close")).clicked() { *should_close = true; self.update_state = UpdateState::Idle; } diff --git a/tests/core/i18n_tests.rs b/tests/core/i18n_tests.rs new file mode 100644 index 0000000..9b499fb --- /dev/null +++ b/tests/core/i18n_tests.rs @@ -0,0 +1,225 @@ +//! Tests for internationalization (i18n) functionality +//! +//! Tests cover: +//! - Language enum methods (locale_code, display_name, all) +//! - Default language selection +//! - Serialization/deserialization +//! - Translation loading via rust-i18n + +use ultralog::i18n::Language; + +// ============================================ +// Language Enum Basic Tests +// ============================================ + +#[test] +fn test_language_default_is_english() { + let lang = Language::default(); + assert_eq!(lang, Language::English); +} + +#[test] +fn test_language_english_locale_code() { + assert_eq!(Language::English.locale_code(), "en"); +} + +#[test] +fn test_language_spanish_locale_code() { + assert_eq!(Language::Spanish.locale_code(), "es"); +} + +#[test] +fn test_language_english_display_name() { + assert_eq!(Language::English.display_name(), "English"); +} + +#[test] +fn test_language_spanish_display_name() { + // Display name should be in the native language + assert_eq!(Language::Spanish.display_name(), "Español"); +} + +#[test] +fn test_language_all_returns_all_languages() { + let all = Language::all(); + assert_eq!(all.len(), 15); + assert!(all.contains(&Language::English)); + assert!(all.contains(&Language::Spanish)); + assert!(all.contains(&Language::German)); + assert!(all.contains(&Language::French)); + assert!(all.contains(&Language::Italian)); + assert!(all.contains(&Language::PortugueseBrazil)); + assert!(all.contains(&Language::PortuguesePortugal)); + assert!(all.contains(&Language::ChineseSimplified)); + assert!(all.contains(&Language::Hindi)); + assert!(all.contains(&Language::Arabic)); + assert!(all.contains(&Language::Bengali)); + assert!(all.contains(&Language::Russian)); + assert!(all.contains(&Language::Urdu)); + assert!(all.contains(&Language::Indonesian)); + assert!(all.contains(&Language::Japanese)); +} + +#[test] +fn test_language_all_english_first() { + // English should be first in the list as the default + let all = Language::all(); + assert_eq!(all[0], Language::English); +} + +// ============================================ +// Language Equality Tests +// ============================================ + +#[test] +fn test_language_equality() { + assert_eq!(Language::English, Language::English); + assert_eq!(Language::Spanish, Language::Spanish); + assert_ne!(Language::English, Language::Spanish); +} + +#[test] +fn test_language_clone() { + let lang = Language::Spanish; + let cloned = lang; + assert_eq!(lang, cloned); +} + +#[test] +fn test_language_copy() { + let lang = Language::English; + let copied = lang; + // Both should still be usable (Copy trait) + assert_eq!(lang.locale_code(), "en"); + assert_eq!(copied.locale_code(), "en"); +} + +// ============================================ +// Language Serialization Tests +// ============================================ + +#[test] +fn test_language_serialize_english() { + let lang = Language::English; + let json = serde_json::to_string(&lang).unwrap(); + assert_eq!(json, "\"English\""); +} + +#[test] +fn test_language_serialize_spanish() { + let lang = Language::Spanish; + let json = serde_json::to_string(&lang).unwrap(); + assert_eq!(json, "\"Spanish\""); +} + +#[test] +fn test_language_deserialize_english() { + let json = "\"English\""; + let lang: Language = serde_json::from_str(json).unwrap(); + assert_eq!(lang, Language::English); +} + +#[test] +fn test_language_deserialize_spanish() { + let json = "\"Spanish\""; + let lang: Language = serde_json::from_str(json).unwrap(); + assert_eq!(lang, Language::Spanish); +} + +#[test] +fn test_language_roundtrip_serialization() { + for lang in Language::all() { + let json = serde_json::to_string(lang).unwrap(); + let deserialized: Language = serde_json::from_str(&json).unwrap(); + assert_eq!(*lang, deserialized); + } +} + +// ============================================ +// Language Debug Tests +// ============================================ + +#[test] +fn test_language_debug_english() { + let debug = format!("{:?}", Language::English); + assert_eq!(debug, "English"); +} + +#[test] +fn test_language_debug_spanish() { + let debug = format!("{:?}", Language::Spanish); + assert_eq!(debug, "Spanish"); +} + +// ============================================ +// Locale Code Consistency Tests +// ============================================ + +#[test] +fn test_locale_codes_are_valid_bcp47() { + // Locale codes follow BCP 47 format: language code (2 chars) optionally followed by + // a hyphen and region code (e.g., "en", "pt-BR", "zh-CN") + for lang in Language::all() { + let code = lang.locale_code(); + assert!(!code.is_empty(), "Locale code should not be empty"); + + // Split by hyphen to check format + let parts: Vec<&str> = code.split('-').collect(); + assert!( + parts.len() <= 2, + "Locale code should have at most 2 parts: {:?}", + code + ); + + // First part should be 2 lowercase letters (language code) + assert_eq!( + parts[0].len(), + 2, + "Language code should be 2 characters: {:?}", + code + ); + assert!( + parts[0].chars().all(|c| c.is_ascii_lowercase()), + "Language code should be lowercase ASCII: {:?}", + code + ); + + // If there's a second part, it should be a region code (2 uppercase or mixed case) + if parts.len() == 2 { + assert!( + parts[1].len() >= 2, + "Region code should be at least 2 characters: {:?}", + code + ); + } + } +} + +#[test] +fn test_all_languages_have_unique_locale_codes() { + let all = Language::all(); + let codes: Vec<&str> = all.iter().map(|l| l.locale_code()).collect(); + + // Check for duplicates + let mut unique_codes = codes.clone(); + unique_codes.sort(); + unique_codes.dedup(); + + assert_eq!( + codes.len(), + unique_codes.len(), + "All locale codes should be unique" + ); +} + +#[test] +fn test_all_languages_have_non_empty_display_names() { + for lang in Language::all() { + let name = lang.display_name(); + assert!( + !name.is_empty(), + "Display name should not be empty for {:?}", + lang + ); + } +} diff --git a/tests/core/mod.rs b/tests/core/mod.rs index e9b82b8..7272c2a 100644 --- a/tests/core/mod.rs +++ b/tests/core/mod.rs @@ -6,9 +6,13 @@ //! - Unit conversions //! - State management //! - Computed channels +//! - Internationalization (i18n) +//! - User settings persistence pub mod computed_channels_tests; pub mod expression_tests; +pub mod i18n_tests; pub mod normalize_tests; +pub mod settings_tests; pub mod state_tests; pub mod units_tests; diff --git a/tests/core/settings_tests.rs b/tests/core/settings_tests.rs new file mode 100644 index 0000000..57b6399 --- /dev/null +++ b/tests/core/settings_tests.rs @@ -0,0 +1,268 @@ +//! Tests for user settings persistence +//! +//! Tests cover: +//! - Default settings values +//! - Serialization/deserialization +//! - Settings roundtrip +//! - Config path handling + +use ultralog::i18n::Language; +use ultralog::settings::UserSettings; + +// ============================================ +// Default Settings Tests +// ============================================ + +#[test] +fn test_settings_default_version() { + let settings = UserSettings::default(); + assert_eq!(settings.version, 1); +} + +#[test] +fn test_settings_default_language() { + let settings = UserSettings::default(); + assert_eq!(settings.language, Language::English); +} + +#[test] +fn test_settings_default_is_consistent() { + let settings1 = UserSettings::default(); + let settings2 = UserSettings::default(); + + assert_eq!(settings1.version, settings2.version); + assert_eq!(settings1.language, settings2.language); +} + +// ============================================ +// Serialization Tests +// ============================================ + +#[test] +fn test_settings_serialize_default() { + let settings = UserSettings::default(); + let json = serde_json::to_string(&settings).unwrap(); + + // Should contain version and language fields + assert!(json.contains("version")); + assert!(json.contains("language")); +} + +#[test] +fn test_settings_serialize_pretty() { + let settings = UserSettings::default(); + let json = serde_json::to_string_pretty(&settings).unwrap(); + + // Pretty format should have newlines + assert!(json.contains('\n')); +} + +#[test] +fn test_settings_deserialize_default() { + let json = r#"{"version":1,"language":"English"}"#; + let settings: UserSettings = serde_json::from_str(json).unwrap(); + + assert_eq!(settings.version, 1); + assert_eq!(settings.language, Language::English); +} + +#[test] +fn test_settings_deserialize_spanish() { + let json = r#"{"version":1,"language":"Spanish"}"#; + let settings: UserSettings = serde_json::from_str(json).unwrap(); + + assert_eq!(settings.version, 1); + assert_eq!(settings.language, Language::Spanish); +} + +#[test] +fn test_settings_deserialize_missing_version() { + // Version should default to 1 if missing + let json = r#"{"language":"Spanish"}"#; + let settings: UserSettings = serde_json::from_str(json).unwrap(); + + assert_eq!(settings.version, 1); + assert_eq!(settings.language, Language::Spanish); +} + +#[test] +fn test_settings_deserialize_missing_language() { + // Language should default to English if missing + let json = r#"{"version":1}"#; + let settings: UserSettings = serde_json::from_str(json).unwrap(); + + assert_eq!(settings.version, 1); + assert_eq!(settings.language, Language::English); +} + +#[test] +fn test_settings_deserialize_empty_object() { + // All fields should use defaults for empty object + let json = r#"{}"#; + let settings: UserSettings = serde_json::from_str(json).unwrap(); + + assert_eq!(settings.version, 1); + assert_eq!(settings.language, Language::English); +} + +#[test] +fn test_settings_roundtrip() { + let original = UserSettings { + version: 1, + language: Language::Spanish, + }; + + let json = serde_json::to_string(&original).unwrap(); + let restored: UserSettings = serde_json::from_str(&json).unwrap(); + + assert_eq!(original.version, restored.version); + assert_eq!(original.language, restored.language); +} + +#[test] +fn test_settings_roundtrip_all_languages() { + for lang in Language::all() { + let settings = UserSettings { + version: 1, + language: *lang, + }; + + let json = serde_json::to_string(&settings).unwrap(); + let restored: UserSettings = serde_json::from_str(&json).unwrap(); + + assert_eq!(settings.language, restored.language); + } +} + +// ============================================ +// Config Path Tests +// ============================================ + +#[test] +fn test_config_dir_returns_some() { + // On most systems, this should return Some + // (unless running in a very restricted environment) + let config_dir = UserSettings::get_config_dir(); + + // This test may need to be adjusted based on environment + // We just verify it doesn't panic + let _ = config_dir; +} + +#[test] +fn test_settings_path_returns_some() { + let settings_path = UserSettings::get_settings_path(); + + // This test may need to be adjusted based on environment + // We just verify it doesn't panic + let _ = settings_path; +} + +#[test] +fn test_settings_path_ends_with_json() { + if let Some(path) = UserSettings::get_settings_path() { + let path_str = path.to_string_lossy(); + assert!( + path_str.ends_with(".json"), + "Settings path should end with .json" + ); + } +} + +#[test] +fn test_settings_path_contains_settings_filename() { + if let Some(path) = UserSettings::get_settings_path() { + let filename = path.file_name().unwrap().to_string_lossy(); + assert_eq!(filename, "settings.json"); + } +} + +#[test] +fn test_config_dir_contains_ultralog() { + if let Some(path) = UserSettings::get_config_dir() { + let path_str = path.to_string_lossy().to_lowercase(); + assert!( + path_str.contains("ultralog"), + "Config dir should contain 'ultralog' or 'UltraLog'" + ); + } +} + +// ============================================ +// Load Tests (non-destructive) +// ============================================ + +#[test] +fn test_load_returns_valid_settings() { + // load() should always return valid settings, even if file doesn't exist + let settings = UserSettings::load(); + + // Should have valid version + assert!(settings.version >= 1); + + // Should have a valid language + assert!(Language::all().contains(&settings.language)); +} + +#[test] +fn test_load_is_idempotent() { + let settings1 = UserSettings::load(); + let settings2 = UserSettings::load(); + + assert_eq!(settings1.version, settings2.version); + // Note: language may have been changed by the user between loads in real usage + // but in tests, it should be consistent +} + +// ============================================ +// Clone and Debug Tests +// ============================================ + +#[test] +fn test_settings_clone() { + let original = UserSettings { + version: 1, + language: Language::Spanish, + }; + + let cloned = original.clone(); + + assert_eq!(original.version, cloned.version); + assert_eq!(original.language, cloned.language); +} + +#[test] +fn test_settings_debug() { + let settings = UserSettings::default(); + let debug = format!("{:?}", settings); + + assert!(debug.contains("UserSettings")); + assert!(debug.contains("version")); + assert!(debug.contains("language")); +} + +// ============================================ +// Future Migration Tests +// ============================================ + +#[test] +fn test_settings_version_for_migration() { + let settings = UserSettings::default(); + + // Current version should be 1 + assert_eq!(settings.version, 1); + + // Version should be serialized for future migration support + let json = serde_json::to_string(&settings).unwrap(); + assert!(json.contains("\"version\":1")); +} + +#[test] +fn test_settings_handles_unknown_fields() { + // Settings should ignore unknown fields for forward compatibility + let json = r#"{"version":1,"language":"English","unknown_field":"value"}"#; + let result: Result = serde_json::from_str(json); + + // Should successfully deserialize, ignoring unknown field + assert!(result.is_ok()); +}