Multilingual support via Fluent + Frontend improvements + Rewrite of ArcReactor

This commit is contained in:
Priler
2026-01-07 05:04:04 +05:00
parent adec595cfa
commit 412acb7e2d
41 changed files with 2436 additions and 723 deletions

View File

@@ -6,6 +6,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use jarvis_core::{
audio, audio_processing, commands, config, db, listener, recorder, stt, intent,
ipc::{self, IpcAction},
i18n,
APP_CONFIG_DIR, APP_LOG_DIR, COMMANDS_LIST, DB,
};
@@ -40,6 +41,9 @@ fn main() -> Result<(), String> {
DB.set(Arc::new(RwLock::new(db::init_settings())))
.expect("DB already initialized");
// init i18n
i18n::init(&DB.get().unwrap().read().language);
// initialize tray
// @TODO. macOS currently not supported for tray functionality,
// due to the separate thread in which tray processing works,

View File

@@ -10,7 +10,7 @@ use image;
#[cfg(target_os="windows")]
use winit::platform::windows::EventLoopBuilderExtWindows;
use jarvis_core::config;
use jarvis_core::{config, i18n};
const TRAY_ICON_BYTES: &[u8] = include_bytes!("../../../resources/icons/32x32.png");
@@ -21,22 +21,22 @@ pub fn init_blocking() {
let icon = load_icon_from_bytes(TRAY_ICON_BYTES);
// form tray menu
let tray_menu = Menu::with_items(&[
&MenuItem::new("Перезапуск", true, None),
&MenuItem::new("Настройки", true, None),
&MenuItem::new("Выход", true, None),
])
.unwrap();
// let tray_menu = Menu::with_items(&[
// &MenuItem::new("Перезапуск", true, None),
// &MenuItem::new("Настройки", true, None),
// &MenuItem::new("Выход", true, None),
// ])
// .unwrap();
let tray_menu = Menu::with_items(&[
&MenuItem::with_id("restart", "Перезапуск", true, None),
&MenuItem::with_id("settings", "Настройки", true, None),
&MenuItem::with_id("exit", "Выход", true, None),
&MenuItem::with_id("restart", i18n::t("tray-restart"), true, None),
&MenuItem::with_id("settings", i18n::t("tray-settings"), true, None),
&MenuItem::with_id("exit", i18n::t("tray-exit"), true, None),
]).unwrap();
let _tray_icon = TrayIconBuilder::new()
.with_menu(Box::new(tray_menu))
.with_tooltip(config::TRAY_TOOLTIP)
.with_tooltip(i18n::t("tray-tooltip"))
.with_icon(icon)
.build()
.unwrap();

View File

@@ -26,6 +26,9 @@ sha2.workspace = true
nnnoiseless = { workspace = true, optional = true }
tokio-tungstenite = { workspace = true, optional = true }
futures-util = { workspace = true, optional = true }
fluent.workspace = true
fluent-bundle.workspace = true
unic-langid.workspace = true
# pv_recorder = { workspace = true, optional = true }
vosk = { version = "0.3.1", optional = true }

View File

@@ -80,6 +80,8 @@ pub const AUTHOR_NAME: Option<&str> = option_env!("CARGO_PKG_AUTHORS");
pub const REPOSITORY_LINK: Option<&str> = option_env!("CARGO_PKG_REPOSITORY");
pub const TG_OFFICIAL_LINK: Option<&str> = Some("https://t.me/howdyho_official");
pub const FEEDBACK_LINK: Option<&str> = Some("https://t.me/jarvis_feedback_bot");
pub const SUPPORT_BOOSTY_LINK: Option<&str> = Some("https://boosty.to/howdyho");
pub const SUPPORT_PATREON_LINK: Option<&str> = Some("https://www.patreon.com/user?u=22843414");
/*
Tray.

View File

@@ -22,6 +22,8 @@ pub struct Settings {
pub vad: VadBackend,
pub gain_normalizer: bool,
pub language: String,
pub api_keys: ApiKeys,
}
@@ -41,6 +43,8 @@ impl Default for Settings {
vad: config::DEFAULT_VAD,
gain_normalizer: config::DEFAULT_GAIN_NORMALIZER,
language: String::from("ru"),
api_keys: ApiKeys {
picovoice: String::from(""),
openai: String::from(""),

View File

@@ -0,0 +1,176 @@
use fluent_bundle::{FluentBundle, FluentResource, FluentArgs, FluentValue};
use fluent_bundle::concurrent::FluentBundle as ConcurrentFluentBundle;
use once_cell::sync::OnceCell;
use parking_lot::RwLock;
use std::collections::HashMap;
use unic_langid::LanguageIdentifier;
// locale files embedded at compile time
const LOCALE_RU: &str = include_str!("i18n/locales/ru.ftl");
const LOCALE_EN: &str = include_str!("i18n/locales/en.ftl");
const LOCALE_UA: &str = include_str!("i18n/locales/ua.ftl");
pub const SUPPORTED_LANGUAGES: &[&str] = &["ru", "en", "ua"];
pub const DEFAULT_LANGUAGE: &str = "ru";
// use concurrent bundle (thread-safe)
type Bundle = ConcurrentFluentBundle<FluentResource>;
static BUNDLES: OnceCell<HashMap<String, Bundle>> = OnceCell::new();
static CURRENT_LANG: OnceCell<RwLock<String>> = OnceCell::new();
// Initialize i18n system
pub fn init(lang: &str) {
let bundles = create_bundles();
BUNDLES.set(bundles).ok();
let lang = if SUPPORTED_LANGUAGES.contains(&lang) { lang } else { DEFAULT_LANGUAGE };
CURRENT_LANG.set(RwLock::new(lang.to_string())).ok();
info!("i18n initialized with language: {}", lang);
}
fn create_bundles() -> HashMap<String, Bundle> {
let mut bundles = HashMap::new();
bundles.insert("ru".to_string(), create_bundle("ru", LOCALE_RU));
bundles.insert("en".to_string(), create_bundle("en", LOCALE_EN));
bundles.insert("ua".to_string(), create_bundle("ua", LOCALE_UA));
bundles
}
fn create_bundle(lang: &str, source: &str) -> Bundle {
let langid: LanguageIdentifier = lang.parse().expect("Invalid language identifier");
let mut bundle = ConcurrentFluentBundle::new_concurrent(vec![langid]);
let resource = FluentResource::try_new(source.to_string())
.expect("Failed to parse FTL resource");
bundle.add_resource(resource).expect("Failed to add resource");
bundle
}
// Set current language
pub fn set_language(lang: &str) {
if let Some(current) = CURRENT_LANG.get() {
let lang = if SUPPORTED_LANGUAGES.contains(&lang) { lang } else { DEFAULT_LANGUAGE };
*current.write() = lang.to_string();
info!("Language changed to: {}", lang);
}
}
// Get current language
pub fn get_language() -> String {
CURRENT_LANG.get()
.map(|l| l.read().clone())
.unwrap_or_else(|| DEFAULT_LANGUAGE.to_string())
}
// Translate a key
pub fn t(key: &str) -> String {
t_with_args(key, None)
}
// Translate a key with arguments
pub fn t_with_args(key: &str, args: Option<&FluentArgs>) -> String {
let lang = get_language();
let bundles = match BUNDLES.get() {
Some(b) => b,
None => return key.to_string(),
};
let bundle = match bundles.get(&lang) {
Some(b) => b,
None => bundles.get(DEFAULT_LANGUAGE).unwrap(),
};
let msg = match bundle.get_message(key) {
Some(m) => m,
None => return key.to_string(),
};
let pattern = match msg.value() {
Some(p) => p,
None => return key.to_string(),
};
let mut errors = vec![];
let result = bundle.format_pattern(pattern, args, &mut errors);
if !errors.is_empty() {
warn!("i18n errors for key '{}': {:?}", key, errors);
}
result.to_string()
}
// Translate with a single argument
pub fn t_arg(key: &str, arg_name: &str, arg_value: &str) -> String {
let mut args = FluentArgs::new();
args.set(arg_name, FluentValue::from(arg_value));
t_with_args(key, Some(&args))
}
// Translate with numeric argument
pub fn t_count(key: &str, count: i64) -> String {
let mut args = FluentArgs::new();
args.set("count", FluentValue::from(count));
t_with_args(key, Some(&args))
}
// Get all translations for current language (for frontend)
pub fn get_all_translations() -> HashMap<String, String> {
let lang = get_language();
get_translations_for(&lang)
}
/// Get all translations for a specific language
pub fn get_translations_for(lang: &str) -> HashMap<String, String> {
let mut result = HashMap::new();
let bundles = match BUNDLES.get() {
Some(b) => b,
None => return result,
};
let bundle = match bundles.get(lang) {
Some(b) => b,
None => match bundles.get(DEFAULT_LANGUAGE) {
Some(b) => b,
None => return result,
},
};
// get source for this language and extract all keys
let source = match lang {
"ru" => LOCALE_RU,
"en" => LOCALE_EN,
"ua" => LOCALE_UA,
_ => LOCALE_RU,
};
// parse keys from FTL source (lines that have "=" and don't start with "#" or "-")
for line in source.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') || line.starts_with('-') {
continue;
}
if let Some(key) = line.split('=').next() {
let key = key.trim();
if !key.is_empty() && !key.contains(' ') {
if let Some(msg) = bundle.get_message(key) {
if let Some(pattern) = msg.value() {
let mut errors = vec![];
let value = bundle.format_pattern(pattern, None, &mut errors);
result.insert(key.to_string(), value.to_string());
}
}
}
}
}
result
}

View File

@@ -0,0 +1,119 @@
# ### APP INFO
app-name = JARVIS
app-description = Voice Assistant
# ### TRAY MENU
tray-restart = Restart
tray-settings = Settings
tray-exit = Exit
tray-tooltip = JARVIS - Voice Assistant
# ### HEADER
header-commands = COMMANDS
header-settings = SETTINGS
# ### SEARCH
search-placeholder = Enter a command manually or say «Jarvis» ...
# ### MAIN PAGE
assistant-not-running = ASSISTANT NOT RUNNING
assistant-offline-hint = You can configure it without starting.
btn-start = START
btn-starting = STARTING...
# ### STATUS
status-disconnected = Disconnected
status-standby = Standby
status-listening = Listening...
status-processing = Processing...
# ### STATS
stats-microphone = MICROPHONE
stats-neural-networks = NEURAL NETWORKS
stats-resources = RESOURCES
stats-system-default = System Default
stats-not-selected = Not selected
stats-loading = Loading...
# ### FOOTER
footer-author = Project author
footer-telegram = Our Telegram channel
footer-github = Github repository
footer-support = Support the project on
# ### SETTINGS
settings-title = Settings
settings-general = General
settings-devices = Devices
settings-neural-networks = Neural Networks
settings-audio = Audio
settings-recognition = Recognition
settings-about = About
settings-language = Language
settings-microphone = Microphone
settings-microphone-desc = The assistant will listen to this microphone.
settings-mic-default = Default (System)
settings-voice = Assistant voice
settings-voice-desc = Not all commands work with all sound packs.
settings-wake-word-engine = Wake word engine
settings-wake-word-desc = Choose the engine for wake word recognition.
settings-stt-engine = Speech recognition
settings-intent-engine = Intent recognition
settings-intent-engine-desc = Select neural network for command recognition.
settings-noise-suppression = Noise suppression
settings-noise-suppression-desc = Reduces background noise.
settings-vad = Voice detection (VAD)
settings-vad-desc = Skips silence, saves CPU resources.
settings-gain-normalizer = Gain normalizer
settings-gain-normalizer-desc = Automatically adjusts volume level.
settings-api-keys = API Keys
settings-save = Save
settings-cancel = Cancel
settings-back = Back
settings-enabled = Enabled
settings-disabled = Disabled
# settings - beta notice
settings-beta-title = BETA version!
settings-beta-desc = Some features may not work correctly.
settings-beta-feedback = Report all bugs to
settings-beta-bot = our Telegram bot
settings-open-logs = Open logs folder
# settings - picovoice
settings-attention = Attention!
settings-picovoice-warning = This neural network doesn't work for everyone!
settings-picovoice-waiting = We are waiting for an official patch from the developers.
settings-picovoice-key-desc = Enter your Picovoice key here. It is issued for free upon registration at
settings-picovoice-key = Picovoice Key
# settings - vosk
settings-auto-detect = Auto-detect
settings-vosk-model = Speech recognition model (Vosk)
settings-vosk-model-desc = Select Vosk model for speech recognition.
settings-models-not-found = Models not found
settings-models-hint = Place Vosk models in resources/vosk folder
# settings - openai
settings-openai-key = OpenAI Key
settings-openai-not-supported = ChatGPT is not currently supported. It will be added in future updates.
# ### COMMANDS PAGE
commands-title = Commands
commands-search = Search commands...
commands-count = { $count } commands
commands-wip-title = [404] This section is under development!
commands-wip-desc = Here will be a list of commands + full-featured command editor.
commands-wip-follow = Follow updates in
commands-wip-channel = our Telegram channel
# ### ERRORS
error-generic = An error occurred
error-connection = Connection error
error-not-found = Not found
# ### NOTIFICATIONS
notification-saved = Settings saved!
notification-error = Error
notification-assistant-started = Assistant started
notification-assistant-stopped = Assistant stopped

View File

@@ -0,0 +1,119 @@
# ### APP INFO
app-name = JARVIS
app-description = Голосовой ассистент
# ### TRAY MENU
tray-restart = Перезапустить
tray-settings = Настройки
tray-exit = Выход
tray-tooltip = JARVIS - Голосовой ассистент
# ### HEADER
header-commands = КОМАНДЫ
header-settings = НАСТРОЙКИ
# ### SEARCH
search-placeholder = Введите команду вручную или произнесите «Джарвис» ...
# ### MAIN PAGE
assistant-not-running = АССИСТЕНТ НЕ ЗАПУЩЕН
assistant-offline-hint = Настроить его можно не запуская.
btn-start = ЗАПУСТИТЬ
btn-starting = ЗАПУСК...
# ### STATUS
status-disconnected = Отключен
status-standby = Ожидание
status-listening = Слушаю...
status-processing = Обработка...
# ### STATS
stats-microphone = МИКРОФОН
stats-neural-networks = НЕЙРОСЕТИ
stats-resources = РЕСУРСЫ
stats-system-default = Системный
stats-not-selected = Не выбран
stats-loading = Загрузка...
# ### FOOTER
footer-author = Автор проекта
footer-telegram = Наш телеграм канал
footer-github = Github репозиторий проекта
footer-support = Поддержать проект на
# ### SETTINGS
settings-title = Настройки
settings-general = Основные
settings-devices = Устройства
settings-neural-networks = Нейросети
settings-audio = Аудио
settings-recognition = Распознавание
settings-about = О программе
settings-language = Язык
settings-microphone = Микрофон
settings-microphone-desc = Его будет слушать ассистент.
settings-mic-default = По умолчанию (Система)
settings-voice = Голос ассистента
settings-voice-desc = Не все команды работают со всеми звуковыми пакетами.
settings-wake-word-engine = Движок активации
settings-wake-word-desc = Выберите нейросеть для распознавания активационной фразы.
settings-stt-engine = Распознавание речи
settings-intent-engine = Определение намерения
settings-intent-engine-desc = Выберите нейросеть для распознавания команд.
settings-noise-suppression = Шумоподавление
settings-noise-suppression-desc = Уменьшает фоновый шум.
settings-vad = Определение голоса (VAD)
settings-vad-desc = Пропускает тишину, экономит ресурсы CPU.
settings-gain-normalizer = Нормализация громкости
settings-gain-normalizer-desc = Автоматически регулирует уровень громкости.
settings-api-keys = API Ключи
settings-save = Сохранить
settings-cancel = Отмена
settings-back = Назад
settings-enabled = Включено
settings-disabled = Отключено
# settings - beta notice
settings-beta-title = БЕТА версия!
settings-beta-desc = Часть функций может работать некорректно.
settings-beta-feedback = Сообщайте обо всех найденных багах в
settings-beta-bot = наш телеграм бот
settings-open-logs = Открыть папку с логами
# settings - picovoice
settings-attention = Внимание!
settings-picovoice-warning = Эта нейросеть работает не у всех!
settings-picovoice-waiting = Мы ждем официального патча от разработчиков.
settings-picovoice-key-desc = Введите сюда свой ключ Picovoice. Он выдается бесплатно при регистрации в
settings-picovoice-key = Ключ Picovoice
# settings - vosk
settings-auto-detect = Авто-определение
settings-vosk-model = Модель распознавания речи (Vosk)
settings-vosk-model-desc = Выберите модель Vosk для распознавания речи.
settings-models-not-found = Модели не найдены
settings-models-hint = Поместите модели Vosk в папку resources/vosk
# settings - openai
settings-openai-key = Ключ OpenAI
settings-openai-not-supported = В данный момент ChatGPT не поддерживается. Он будет добавлен в ближайших обновлениях.
# ### COMMANDS PAGE
commands-title = Команды
commands-search = Поиск команд...
commands-count = { $count } команд
commands-wip-title = [404] Этот раздел еще находится в разработке!
commands-wip-desc = Тут будет список команд + полноценный редактор команд.
commands-wip-follow = Следите за обновлениями в
commands-wip-channel = нашем телеграм канале
# ### ERRORS
error-generic = Произошла ошибка
error-connection = Ошибка подключения
error-not-found = Не найдено
# ### NOTIFICATIONS
notification-saved = Настройки сохранены!
notification-error = Ошибка
notification-assistant-started = Ассистент запущен
notification-assistant-stopped = Ассистент остановлен

View File

@@ -0,0 +1,119 @@
# ### APP INFO
app-name = JARVIS
app-description = Голосовий асистент
# ### TRAY MENU
tray-restart = Перезапустити
tray-settings = Налаштування
tray-exit = Вихід
tray-tooltip = JARVIS - Голосовий асистент
# ### HEADER
header-commands = КОМАНДИ
header-settings = НАЛАШТУВАННЯ
# ### SEARCH
search-placeholder = Введіть команду вручну або скажіть «Джарвіс» ...
# ### MAIN PAGE
assistant-not-running = АСИСТЕНТ НЕ ЗАПУЩЕНО
assistant-offline-hint = Налаштувати його можна не запускаючи.
btn-start = ЗАПУСТИТИ
btn-starting = ЗАПУСК...
# ### STATUS
status-disconnected = Відключено
status-standby = Очікування
status-listening = Слухаю...
status-processing = Обробка...
# ### STATS
stats-microphone = МІКРОФОН
stats-neural-networks = НЕЙРОМЕРЕЖІ
stats-resources = РЕСУРСИ
stats-system-default = Системний
stats-not-selected = Не вибрано
stats-loading = Завантаження...
# ### FOOTER
footer-author = Автор проєкту
footer-telegram = Наш телеграм канал
footer-github = Github репозиторій проєкту
footer-support = Підтримати проєкт на
# ### SETTINGS
settings-title = Налаштування
settings-general = Основні
settings-devices = Пристрої
settings-neural-networks = Нейромережі
settings-audio = Аудіо
settings-recognition = Розпізнавання
settings-about = Про програму
settings-language = Мова
settings-microphone = Мікрофон
settings-microphone-desc = Його буде слухати асистент.
settings-mic-default = За замовчуванням (Система)
settings-voice = Голос асистента
settings-voice-desc = Не всі команди працюють з усіма звуковими пакетами.
settings-wake-word-engine = Рушій активації
settings-wake-word-desc = Виберіть нейромережу для розпізнавання активаційної фрази.
settings-stt-engine = Розпізнавання мовлення
settings-intent-engine = Визначення наміру
settings-intent-engine-desc = Виберіть нейромережу для розпізнавання команд.
settings-noise-suppression = Шумозаглушення
settings-noise-suppression-desc = Зменшує фоновий шум.
settings-vad = Визначення голосу (VAD)
settings-vad-desc = Пропускає тишу, економить ресурси CPU.
settings-gain-normalizer = Нормалізація гучності
settings-gain-normalizer-desc = Автоматично регулює рівень гучності.
settings-api-keys = API Ключі
settings-save = Зберегти
settings-cancel = Скасувати
settings-back = Назад
settings-enabled = Увімкнено
settings-disabled = Вимкнено
# settings - beta notice
settings-beta-title = БЕТА версія!
settings-beta-desc = Частина функцій може працювати некоректно.
settings-beta-feedback = Повідомляйте про всі знайдені баги в
settings-beta-bot = наш телеграм бот
settings-open-logs = Відкрити папку з логами
# settings - picovoice
settings-attention = Увага!
settings-picovoice-warning = Ця нейромережа працює не у всіх!
settings-picovoice-waiting = Ми чекаємо офіційного патча від розробників.
settings-picovoice-key-desc = Введіть сюди свій ключ Picovoice. Він видається безкоштовно при реєстрації в
settings-picovoice-key = Ключ Picovoice
# settings - vosk
settings-auto-detect = Авто-визначення
settings-vosk-model = Модель розпізнавання мовлення (Vosk)
settings-vosk-model-desc = Виберіть модель Vosk для розпізнавання мовлення.
settings-models-not-found = Моделі не знайдено
settings-models-hint = Помістіть моделі Vosk в папку resources/vosk
# settings - openai
settings-openai-key = Ключ OpenAI
settings-openai-not-supported = Наразі ChatGPT не підтримується. Він буде доданий у наступних оновленнях.
# ### COMMANDS PAGE
commands-title = Команди
commands-search = Пошук команд...
commands-count = { $count } команд
commands-wip-title = [404] Цей розділ ще в розробці!
commands-wip-desc = Тут буде список команд + повноцінний редактор команд.
commands-wip-follow = Слідкуйте за оновленнями в
commands-wip-channel = нашому телеграм каналі
# ### ERRORS
error-generic = Сталася помилка
error-connection = Помилка підключення
error-not-found = Не знайдено
# ### NOTIFICATIONS
notification-saved = Налаштування збережено!
notification-error = Помилка
notification-assistant-started = Асистент запущено
notification-assistant-stopped = Асистент зупинено

View File

@@ -11,6 +11,7 @@ pub mod audio;
pub mod commands;
pub mod config;
pub mod db;
pub mod i18n;
#[cfg(feature = "jarvis_app")]
pub mod listener;

View File

@@ -1,7 +1,7 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use jarvis_core::{config, db, APP_CONFIG_DIR, APP_LOG_DIR, DB};
use jarvis_core::{config, db, i18n, APP_CONFIG_DIR, APP_LOG_DIR, DB};
use parking_lot::RwLock;
use std::sync::Arc;
@@ -26,6 +26,11 @@ fn main() {
// init db
let settings = db::init_settings();
// init i18n
i18n::init(&settings.language);
// set db
DB.set(Arc::new(RwLock::new(settings)))
.expect("DB already initialized");
let db_arc = DB.get().unwrap().clone();
@@ -67,6 +72,13 @@ fn main() {
// vosk
tauri_commands::list_vosk_models,
// i18n
tauri_commands::get_translations,
tauri_commands::translate,
tauri_commands::get_current_language,
tauri_commands::set_language,
tauri_commands::get_supported_languages,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

View File

@@ -25,4 +25,8 @@ pub use sys::*;
// import STT commands
mod stt;
pub use stt::*;
pub use stt::*;
// import i18n commands
mod i18n;
pub use i18n::*;

View File

@@ -15,6 +15,7 @@ pub fn db_read(state: tauri::State<'_, AppState>, key: &str) -> String {
"noise_suppression" => format!("{:?}", settings.noise_suppression),
"vad" => format!("{:?}", settings.vad),
"gain_normalizer" => settings.gain_normalizer.to_string(),
"language" => settings.language.to_string(),
"api_key__picovoice" => settings.api_keys.picovoice.clone(),
"api_key__openai" => settings.api_keys.openai.clone(),
_ => String::new(),
@@ -78,6 +79,9 @@ pub fn db_write(state: tauri::State<'_, AppState>, key: &str, val: &str) -> bool
_ => return false,
}
}
"language" => {
settings.language = val.to_string();
}
"api_key__picovoice" => {
settings.api_keys.picovoice = val.to_string();
}

View File

@@ -38,6 +38,24 @@ pub fn get_tg_official_link() -> String {
}
}
#[tauri::command]
pub fn get_boosty_link() -> String {
if let Some(ver) = config::SUPPORT_BOOSTY_LINK {
ver.to_string()
} else {
String::from("error")
}
}
#[tauri::command]
pub fn get_patreon_link() -> String {
if let Some(ver) = config::SUPPORT_PATREON_LINK {
ver.to_string()
} else {
String::from("error")
}
}
#[tauri::command]
pub fn get_feedback_link() -> String {
if let Some(res) = config::FEEDBACK_LINK {

View File

@@ -0,0 +1,49 @@
use jarvis_core::i18n;
use std::collections::HashMap;
use crate::AppState;
// Get all translations for frontend
#[tauri::command]
pub fn get_translations() -> HashMap<String, String> {
i18n::get_all_translations()
}
// Get single translation
#[tauri::command]
pub fn translate(key: &str) -> String {
i18n::t(key)
}
// Get current language
#[tauri::command]
pub fn get_current_language() -> String {
i18n::get_language()
}
// Set language and get new translations
#[tauri::command]
pub fn set_language(state: tauri::State<'_, AppState>, lang: &str) -> HashMap<String, String> {
// update i18n
i18n::set_language(lang);
// also save to db
{
let mut settings = state.db.write();
settings.language = lang.to_string();
}
// save to disk
let snapshot = state.db.read().clone();
if let Err(e) = jarvis_core::db::save_settings(&snapshot) {
log::error!("Failed to save settings: {}", e);
}
// return new translations
i18n::get_all_translations()
}
// Get supported languages
#[tauri::command]
pub fn get_supported_languages() -> Vec<&'static str> {
i18n::SUPPORTED_LANGUAGES.to_vec()
}

View File

@@ -20,7 +20,7 @@
"resizable": false,
"title": "Jarvis Voice Assistant",
"width": 550,
"height": 700
"height": 800
}
]
},