From 3b831b7bd306d1879023e828dafbd3ca4ceb65ae Mon Sep 17 00:00:00 2001 From: omercelikdev Date: Sun, 12 Jul 2026 22:06:55 +0300 Subject: [PATCH] i18n: localize the native tray menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tray ("Show QlipLab" / "Quit") is built in Rust at startup, before the webview and i18n exist, so it was always English. Added a `set_tray_labels(show, quit)` command that relabels the kept menu-item handles; the frontend calls it once i18n initialises and again on every `languageChanged`, so the tray follows the app language. Rust verified with `cargo check`. i18n: tray.show / tray.quit ×15 (the brand name QlipLab stays in "Show QlipLab"). Co-Authored-By: Claude Opus 4.8 --- src-tauri/src/lib.rs | 27 +++++++++++++++++++++++++++ src/App.tsx | 12 ++++++++++++ src/i18n/locales/ar.json | 4 +++- src/i18n/locales/de.json | 4 +++- src/i18n/locales/en.json | 4 +++- src/i18n/locales/es.json | 4 +++- src/i18n/locales/fr.json | 4 +++- src/i18n/locales/hi.json | 4 +++- src/i18n/locales/it.json | 4 +++- src/i18n/locales/ja.json | 4 +++- src/i18n/locales/ko.json | 4 +++- src/i18n/locales/nl.json | 4 +++- src/i18n/locales/pl.json | 4 +++- src/i18n/locales/pt.json | 4 +++- src/i18n/locales/ru.json | 4 +++- src/i18n/locales/tr.json | 4 +++- src/i18n/locales/zh.json | 4 +++- 17 files changed, 84 insertions(+), 15 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 9281717..10d72df 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -599,6 +599,26 @@ fn simulate_paste_in_place() -> Result<(), String> { // --- Trigger Engine: Keystroke monitoring --- +/// Tray menu item handles, kept so the frontend can relabel them once i18n has +/// loaded (the tray is built in Rust before the webview, so it starts English). +struct TrayMenuState { + show: tauri::menu::MenuItem, + quit: tauri::menu::MenuItem, +} + +/// Relabel the tray menu in the user's language. Called by the frontend after +/// i18n initialises and whenever the language changes. +#[tauri::command] +fn set_tray_labels( + state: tauri::State<'_, TrayMenuState>, + show: String, + quit: String, +) -> Result<(), String> { + state.show.set_text(show).map_err(|e| e.to_string())?; + state.quit.set_text(quit).map_err(|e| e.to_string())?; + Ok(()) +} + /// Shared state for the trigger engine (source-agnostic: snippets, vault, etc.) struct TriggerEngineState { /// Map of trigger_text → source_id (e.g. "snippet:uuid" or "vault:uuid:field") @@ -1630,6 +1650,7 @@ pub fn run() { get_frontmost_app, show_panel, hide_panel, + set_tray_labels, ocr_image, simulate_backspace, update_triggers, @@ -1662,6 +1683,12 @@ pub fn run() { .item(&quit_item) .build()?; + // Keep handles so the frontend can localise the labels post-i18n. + app.manage(TrayMenuState { + show: show_item.clone(), + quit: quit_item.clone(), + }); + let mut tray_builder = TrayIconBuilder::new(); // macOS: Use template image (black on transparent, system adapts color) diff --git a/src/App.tsx b/src/App.tsx index 8d71018..c2835b8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,6 +2,7 @@ import { useEffect, useState, useCallback } from 'react'; import { AnimatePresence } from 'framer-motion'; import { getCurrentWindow, PhysicalPosition, type CloseRequestedEvent } from '@tauri-apps/api/window'; import { listen } from '@tauri-apps/api/event'; +import { invoke } from '@tauri-apps/api/core'; import { Sidebar } from './components/layout/Sidebar'; import { SearchBar } from './components/layout/DragBar'; import { HintBar } from './components/layout/HintBar'; @@ -59,6 +60,17 @@ function App() { saveListWidth(width); }, []); + // Localise the native tray menu, which Rust builds in English before the + // webview (and thus i18n) exists. Re-sync whenever the language changes. + useEffect(() => { + const syncTray = () => { + invoke('set_tray_labels', { show: i18n.t('tray.show'), quit: i18n.t('tray.quit') }).catch(() => {}); + }; + syncTray(); + i18n.on('languageChanged', syncTray); + return () => { i18n.off('languageChanged', syncTray); }; + }, []); + useEffect(() => { const init = async () => { await initDatabase(); diff --git a/src/i18n/locales/ar.json b/src/i18n/locales/ar.json index de48e56..648820c 100644 --- a/src/i18n/locales/ar.json +++ b/src/i18n/locales/ar.json @@ -447,5 +447,7 @@ "vault.dialog.bank.swift": "SWIFT (اختياري)", "vault.dialog.personal.countryPlaceholder": "الدولة", "vault.dialog.personal.popular": "شائع", - "vault.dialog.personal.allCountries": "كل الدول" + "vault.dialog.personal.allCountries": "كل الدول", + "tray.show": "إظهار QlipLab", + "tray.quit": "إنهاء" } diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index 0360d0c..4f93464 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -447,5 +447,7 @@ "vault.dialog.bank.swift": "SWIFT (optional)", "vault.dialog.personal.countryPlaceholder": "Land", "vault.dialog.personal.popular": "Beliebt", - "vault.dialog.personal.allCountries": "Alle Länder" + "vault.dialog.personal.allCountries": "Alle Länder", + "tray.show": "QlipLab anzeigen", + "tray.quit": "Beenden" } diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 1578852..ecc35fe 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -447,5 +447,7 @@ "vault.dialog.bank.swift": "SWIFT (optional)", "vault.dialog.personal.countryPlaceholder": "Country", "vault.dialog.personal.popular": "Popular", - "vault.dialog.personal.allCountries": "All Countries" + "vault.dialog.personal.allCountries": "All Countries", + "tray.show": "Show QlipLab", + "tray.quit": "Quit" } diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index 02d0586..04fdc1f 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json @@ -447,5 +447,7 @@ "vault.dialog.bank.swift": "SWIFT (opcional)", "vault.dialog.personal.countryPlaceholder": "País", "vault.dialog.personal.popular": "Populares", - "vault.dialog.personal.allCountries": "Todos los países" + "vault.dialog.personal.allCountries": "Todos los países", + "tray.show": "Mostrar QlipLab", + "tray.quit": "Salir" } diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 09cd0de..783f32f 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -447,5 +447,7 @@ "vault.dialog.bank.swift": "SWIFT (facultatif)", "vault.dialog.personal.countryPlaceholder": "Pays", "vault.dialog.personal.popular": "Populaires", - "vault.dialog.personal.allCountries": "Tous les pays" + "vault.dialog.personal.allCountries": "Tous les pays", + "tray.show": "Afficher QlipLab", + "tray.quit": "Quitter" } diff --git a/src/i18n/locales/hi.json b/src/i18n/locales/hi.json index 80b8d0e..d6f798c 100644 --- a/src/i18n/locales/hi.json +++ b/src/i18n/locales/hi.json @@ -447,5 +447,7 @@ "vault.dialog.bank.swift": "SWIFT (वैकल्पिक)", "vault.dialog.personal.countryPlaceholder": "देश", "vault.dialog.personal.popular": "लोकप्रिय", - "vault.dialog.personal.allCountries": "सभी देश" + "vault.dialog.personal.allCountries": "सभी देश", + "tray.show": "QlipLab दिखाएं", + "tray.quit": "बाहर निकलें" } diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json index 2ea92c6..3dcd430 100644 --- a/src/i18n/locales/it.json +++ b/src/i18n/locales/it.json @@ -447,5 +447,7 @@ "vault.dialog.bank.swift": "SWIFT (facoltativo)", "vault.dialog.personal.countryPlaceholder": "Paese", "vault.dialog.personal.popular": "Popolari", - "vault.dialog.personal.allCountries": "Tutti i paesi" + "vault.dialog.personal.allCountries": "Tutti i paesi", + "tray.show": "Mostra QlipLab", + "tray.quit": "Esci" } diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 4d2c4fa..96b7eb3 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -447,5 +447,7 @@ "vault.dialog.bank.swift": "SWIFT(任意)", "vault.dialog.personal.countryPlaceholder": "国", "vault.dialog.personal.popular": "人気", - "vault.dialog.personal.allCountries": "すべての国" + "vault.dialog.personal.allCountries": "すべての国", + "tray.show": "QlipLab を表示", + "tray.quit": "終了" } diff --git a/src/i18n/locales/ko.json b/src/i18n/locales/ko.json index 4ff82c8..3dad2e6 100644 --- a/src/i18n/locales/ko.json +++ b/src/i18n/locales/ko.json @@ -447,5 +447,7 @@ "vault.dialog.bank.swift": "SWIFT (선택)", "vault.dialog.personal.countryPlaceholder": "국가", "vault.dialog.personal.popular": "인기", - "vault.dialog.personal.allCountries": "모든 국가" + "vault.dialog.personal.allCountries": "모든 국가", + "tray.show": "QlipLab 표시", + "tray.quit": "종료" } diff --git a/src/i18n/locales/nl.json b/src/i18n/locales/nl.json index 53e965c..02c64cd 100644 --- a/src/i18n/locales/nl.json +++ b/src/i18n/locales/nl.json @@ -447,5 +447,7 @@ "vault.dialog.bank.swift": "SWIFT (optioneel)", "vault.dialog.personal.countryPlaceholder": "Land", "vault.dialog.personal.popular": "Populair", - "vault.dialog.personal.allCountries": "Alle landen" + "vault.dialog.personal.allCountries": "Alle landen", + "tray.show": "QlipLab tonen", + "tray.quit": "Afsluiten" } diff --git a/src/i18n/locales/pl.json b/src/i18n/locales/pl.json index 22ce55f..2845979 100644 --- a/src/i18n/locales/pl.json +++ b/src/i18n/locales/pl.json @@ -447,5 +447,7 @@ "vault.dialog.bank.swift": "SWIFT (opcjonalnie)", "vault.dialog.personal.countryPlaceholder": "Kraj", "vault.dialog.personal.popular": "Popularne", - "vault.dialog.personal.allCountries": "Wszystkie kraje" + "vault.dialog.personal.allCountries": "Wszystkie kraje", + "tray.show": "Pokaż QlipLab", + "tray.quit": "Zakończ" } diff --git a/src/i18n/locales/pt.json b/src/i18n/locales/pt.json index e28883d..32f2bc0 100644 --- a/src/i18n/locales/pt.json +++ b/src/i18n/locales/pt.json @@ -447,5 +447,7 @@ "vault.dialog.bank.swift": "SWIFT (opcional)", "vault.dialog.personal.countryPlaceholder": "País", "vault.dialog.personal.popular": "Populares", - "vault.dialog.personal.allCountries": "Todos os países" + "vault.dialog.personal.allCountries": "Todos os países", + "tray.show": "Mostrar QlipLab", + "tray.quit": "Sair" } diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index dbd3e25..fc6b2ab 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -447,5 +447,7 @@ "vault.dialog.bank.swift": "SWIFT (необязательно)", "vault.dialog.personal.countryPlaceholder": "Страна", "vault.dialog.personal.popular": "Популярные", - "vault.dialog.personal.allCountries": "Все страны" + "vault.dialog.personal.allCountries": "Все страны", + "tray.show": "Показать QlipLab", + "tray.quit": "Выход" } diff --git a/src/i18n/locales/tr.json b/src/i18n/locales/tr.json index 5e4e701..3212ad5 100644 --- a/src/i18n/locales/tr.json +++ b/src/i18n/locales/tr.json @@ -447,5 +447,7 @@ "vault.dialog.bank.swift": "SWIFT (isteğe bağlı)", "vault.dialog.personal.countryPlaceholder": "Ülke", "vault.dialog.personal.popular": "Popüler", - "vault.dialog.personal.allCountries": "Tüm Ülkeler" + "vault.dialog.personal.allCountries": "Tüm Ülkeler", + "tray.show": "QlipLab'ı Göster", + "tray.quit": "Çıkış" } diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 2792190..ef386a7 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -447,5 +447,7 @@ "vault.dialog.bank.swift": "SWIFT(可选)", "vault.dialog.personal.countryPlaceholder": "国家/地区", "vault.dialog.personal.popular": "常用", - "vault.dialog.personal.allCountries": "所有国家/地区" + "vault.dialog.personal.allCountries": "所有国家/地区", + "tray.show": "显示 QlipLab", + "tray.quit": "退出" }