mirror of
https://github.com/Priler/jarvis.git
synced 2026-05-26 07:08:11 +00:00
Multilingual support via Fluent + Frontend improvements + Rewrite of ArcReactor
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(""),
|
||||
|
||||
176
crates/jarvis-core/src/i18n.rs
Normal file
176
crates/jarvis-core/src/i18n.rs
Normal 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
|
||||
}
|
||||
119
crates/jarvis-core/src/i18n/locales/en.ftl
Normal file
119
crates/jarvis-core/src/i18n/locales/en.ftl
Normal 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
|
||||
119
crates/jarvis-core/src/i18n/locales/ru.ftl
Normal file
119
crates/jarvis-core/src/i18n/locales/ru.ftl
Normal 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 = Ассистент остановлен
|
||||
119
crates/jarvis-core/src/i18n/locales/ua.ftl
Normal file
119
crates/jarvis-core/src/i18n/locales/ua.ftl
Normal 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 = Асистент зупинено
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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::*;
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
49
crates/jarvis-gui/src/tauri_commands/i18n.rs
Normal file
49
crates/jarvis-gui/src/tauri_commands/i18n.rs
Normal 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()
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
"resizable": false,
|
||||
"title": "Jarvis Voice Assistant",
|
||||
"width": 550,
|
||||
"height": 700
|
||||
"height": 800
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user