@@ -28,15 +28,20 @@ use serde_json::Value as Json;
2828use std:: path:: Path ;
2929use std:: path:: PathBuf ;
3030use tauri:: { AppHandle , Manager , async_runtime} ;
31- use tauri_plugin_fs_pro:: { IconOptions , icon, metadata, name } ;
31+ use tauri_plugin_fs_pro:: { IconOptions , icon, metadata} ;
3232use tauri_plugin_global_shortcut:: GlobalShortcutExt ;
3333use tauri_plugin_global_shortcut:: Shortcut ;
3434use tauri_plugin_global_shortcut:: ShortcutEvent ;
3535use tauri_plugin_global_shortcut:: ShortcutState ;
3636use tauri_plugin_store:: StoreExt ;
3737use tokio:: sync:: oneshot:: Sender as OneshotSender ;
3838
39+ // Deprecated. We no longer index this field, but to be backward-compatible, we
40+ // have to keep it.
3941const FIELD_APP_NAME : & str = "app_name" ;
42+
43+ const FIELD_APP_NAME_ZH : & str = "app_name_zh" ;
44+ const FIELD_APP_NAME_EN : & str = "app_name_en" ;
4045const FIELD_ICON_PATH : & str = "icon_path" ;
4146const FIELD_APP_ALIAS : & str = "app_alias" ;
4247const APPLICATION_SEARCH_SOURCE_ID : & str = "application" ;
@@ -96,17 +101,34 @@ fn get_app_path(app: &App) -> String {
96101 . expect ( "should be UTF-8 encoded" )
97102}
98103
99- /// Helper function to return `app`'s path.
100- ///
101- /// * macOS: extract `app_path`'s file name and remove the file extension
102- /// * Windows/Linux: return the name specified in `.desktop` file
103- async fn get_app_name ( app : & App ) -> String {
104- if cfg ! ( any( target_os = "linux" , target_os = "windows" ) ) {
105- app. name . clone ( )
106- } else {
107- let app_path = get_app_path ( app) ;
108- name ( app_path. into ( ) ) . await
104+ /// Helper function to return `app`'s Chinese name.
105+ async fn get_app_name_zh ( app : & App ) -> String {
106+ // First try zh_CN
107+ if let Some ( name) = app. localized_app_names . get ( "zh_CN" ) {
108+ return name. clone ( ) ;
109+ }
110+ // Then try zh_Hans
111+ if let Some ( name) = app. localized_app_names . get ( "zh_Hans" ) {
112+ return name. clone ( ) ;
113+ }
114+
115+ // Fall back to base name
116+ app. name . clone ( )
117+ }
118+
119+ /// Helper function to return `app`'s English name.
120+ async fn get_app_name_en ( app : & App ) -> String {
121+ // First try en_US
122+ if let Some ( name) = app. localized_app_names . get ( "en_US" ) {
123+ return name. clone ( ) ;
109124 }
125+ // Then try en
126+ if let Some ( name) = app. localized_app_names . get ( "en" ) {
127+ return name. clone ( ) ;
128+ }
129+
130+ // Fall back to base name
131+ app. name . clone ( )
110132}
111133
112134/// Helper function to return an absolute path to `app`'s icon.
@@ -202,9 +224,13 @@ async fn index_applications_if_not_indexed(
202224 pizza_engine_builder. set_data_store ( disk_store) ;
203225
204226 let mut schema = Schema :: new ( ) ;
205- let field_app_name = Property :: builder ( FieldType :: Text ) . build ( ) ;
227+ let field_app_name_zh = Property :: builder ( FieldType :: Text ) . build ( ) ;
206228 schema
207- . add_property ( FIELD_APP_NAME , field_app_name)
229+ . add_property ( FIELD_APP_NAME_ZH , field_app_name_zh)
230+ . expect ( "no collision could happen" ) ;
231+ let field_app_name_en = Property :: builder ( FieldType :: Text ) . build ( ) ;
232+ schema
233+ . add_property ( FIELD_APP_NAME_EN , field_app_name_en)
208234 . expect ( "no collision could happen" ) ;
209235 let property_icon = Property :: builder ( FieldType :: Text ) . index ( false ) . build ( ) ;
210236 schema
@@ -249,21 +275,33 @@ async fn index_applications_if_not_indexed(
249275
250276 for app in apps. iter ( ) {
251277 let app_path = get_app_path ( app) ;
252- let app_name = get_app_name ( app) . await ;
278+ let app_name_zh = get_app_name_zh ( app) . await ;
279+ let app_name_en = get_app_name_en ( app) . await ;
253280 let app_icon_path = get_app_icon_path ( & tauri_app_handle, app)
254281 . await
255282 . map_err ( |str| anyhow:: anyhow!( str ) ) ?;
256283 let app_alias = get_app_alias ( & tauri_app_handle, & app_path) . unwrap_or ( String :: new ( ) ) ;
257284
258- if app_name. is_empty ( ) || app_name. eq ( & tauri_app_handle. package_info ( ) . name ) {
285+ // Skip if both names are empty
286+ if app_name_zh. is_empty ( ) && app_name_en. is_empty ( ) {
287+ continue ;
288+ }
289+
290+ // Skip if this is Coco itself
291+ //
292+ // Coco does not have localized app names, so app_name_en and app_name_zh
293+ // should both have value "Coco-AI", so either should work.
294+ if app_name_en == tauri_app_handle. package_info ( ) . name {
259295 continue ;
260296 }
261297
262298 // You cannot write `app_name.clone()` within the `doc!()` macro, we should fix this.
263- let app_name_clone = app_name. clone ( ) ;
299+ let app_name_zh_clone = app_name_zh. clone ( ) ;
300+ let app_name_en_clone = app_name_en. clone ( ) ;
264301 let app_path_clone = app_path. clone ( ) ;
265302 let document = doc ! ( app_path_clone, {
266- FIELD_APP_NAME => app_name_clone,
303+ FIELD_APP_NAME_ZH => app_name_zh_clone,
304+ FIELD_APP_NAME_EN => app_name_en_clone,
267305 FIELD_ICON_PATH => app_icon_path,
268306 FIELD_APP_ALIAS => app_alias,
269307 }
@@ -272,8 +310,8 @@ async fn index_applications_if_not_indexed(
272310 // We don't error out because one failure won't break the whole thing
273311 if let Err ( e) = writer. create_document ( document) . await {
274312 warn ! (
275- "failed to index application [app name: '{}', app path: '{}'] due to error [{}]" ,
276- app_name , app_path, e
313+ "failed to index application [app name zh: '{}', app name en : '{}', app path: '{}'] due to error [{}]" ,
314+ app_name_zh , app_name_en , app_path, e
277315 )
278316 }
279317 }
@@ -402,9 +440,17 @@ impl Task for SearchApplicationsTask {
402440 //
403441 // It will be passed to Pizza like "Google\nChrome". Using Display impl would result
404442 // in an invalid query DSL and serde will complain.
443+ //
444+ // In order to be backward compatible, we still do match and prefix queries to the
445+ // app_name field.
405446 let dsl = format ! (
406- "{{ \" query\" : {{ \" bool\" : {{ \" should\" : [ {{ \" match\" : {{ \" {FIELD_APP_NAME}\" : {:?} }} }}, {{ \" prefix\" : {{ \" {FIELD_APP_NAME}\" : {:?} }} }} ] }} }} }}" ,
407- self . query_string, self . query_string
447+ "{{ \" query\" : {{ \" bool\" : {{ \" should\" : [ {{ \" match\" : {{ \" {FIELD_APP_NAME_ZH}\" : {:?} }} }}, {{ \" prefix\" : {{ \" {FIELD_APP_NAME_ZH}\" : {:?} }} }}, {{ \" match\" : {{ \" {FIELD_APP_NAME_EN}\" : {:?} }} }}, {{ \" prefix\" : {{ \" {FIELD_APP_NAME_EN}\" : {:?} }} }}, {{ \" match\" : {{ \" {FIELD_APP_NAME}\" : {:?} }} }}, {{ \" prefix\" : {{ \" {FIELD_APP_NAME}\" : {:?} }} }} ] }} }} }}" ,
448+ self . query_string,
449+ self . query_string,
450+ self . query_string,
451+ self . query_string,
452+ self . query_string,
453+ self . query_string
408454 ) ;
409455
410456 let state = state
@@ -601,7 +647,7 @@ impl SearchSource for ApplicationSearchSource {
601647
602648 let total_hits = search_result. total_hits ;
603649 let source = self . get_type ( ) ;
604- let hits = pizza_engine_hits_to_coco_hits ( search_result. hits ) ;
650+ let hits = pizza_engine_hits_to_coco_hits ( search_result. hits ) . await ;
605651
606652 Ok ( QueryResponse {
607653 source,
@@ -611,9 +657,11 @@ impl SearchSource for ApplicationSearchSource {
611657 }
612658}
613659
614- fn pizza_engine_hits_to_coco_hits (
660+ async fn pizza_engine_hits_to_coco_hits (
615661 pizza_engine_hits : Option < Vec < PizzaEngineDocument > > ,
616662) -> Vec < ( Document , f64 ) > {
663+ use crate :: util:: app_lang:: { Lang , get_app_lang} ;
664+
617665 let Some ( engine_hits) = pizza_engine_hits else {
618666 return Vec :: new ( ) ;
619667 } ;
@@ -622,10 +670,43 @@ fn pizza_engine_hits_to_coco_hits(
622670 for engine_hit in engine_hits {
623671 let score = engine_hit. score . unwrap_or ( 0.0 ) as f64 ;
624672 let mut document_fields = engine_hit. fields ;
625- let app_name = match document_fields. remove ( FIELD_APP_NAME ) . unwrap ( ) {
626- FieldValue :: Text ( string) => string,
627- _ => unreachable ! ( "field name is of type Text" ) ,
673+
674+ // Get both Chinese and English names
675+ let opt_app_name_zh = match document_fields. remove ( FIELD_APP_NAME_ZH ) {
676+ Some ( FieldValue :: Text ( string) ) => Some ( string) ,
677+ _ => None ,
678+ } ;
679+ let opt_app_name_en = match document_fields. remove ( FIELD_APP_NAME_EN ) {
680+ Some ( FieldValue :: Text ( string) ) => Some ( string) ,
681+ _ => None ,
628682 } ;
683+ let opt_app_name_deprecated = match document_fields. remove ( FIELD_APP_NAME ) {
684+ Some ( FieldValue :: Text ( string) ) => Some ( string) ,
685+ _ => None ,
686+ } ;
687+
688+ let app_name: String = {
689+ if let Some ( legacy_app_name) = opt_app_name_deprecated {
690+ // Old version of index, which only contains the field app_name.
691+ legacy_app_name
692+ } else {
693+ // New version of index store the following 2 fields
694+
695+ let panic_msg = format ! (
696+ "new version of index should contain field [{}] and [{}]" ,
697+ FIELD_APP_NAME_EN , FIELD_APP_NAME_ZH
698+ ) ;
699+ let app_name_zh = opt_app_name_zh. expect ( & panic_msg) ;
700+ let app_name_en = opt_app_name_en. expect ( & panic_msg) ;
701+
702+ // Choose the appropriate name based on current language
703+ match get_app_lang ( ) . await {
704+ Lang :: zh_CN => app_name_zh,
705+ Lang :: en_US => app_name_en,
706+ }
707+ }
708+ } ;
709+
629710 let app_path = engine_hit. key . expect ( "key should be set to app path" ) ;
630711 let app_icon_path = match document_fields. remove ( FIELD_ICON_PATH ) . unwrap ( ) {
631712 FieldValue :: Text ( string) => string,
@@ -645,7 +726,7 @@ fn pizza_engine_hits_to_coco_hits(
645726 } ) ,
646727 id : app_path. clone ( ) ,
647728 category : Some ( "Application" . to_string ( ) ) ,
648- title : Some ( app_name. clone ( ) ) ,
729+ title : Some ( app_name) ,
649730 icon : Some ( app_icon_path) ,
650731 on_opened : Some ( on_opened) ,
651732 url : Some ( url) ,
@@ -1033,15 +1114,24 @@ pub async fn get_app_search_path(tauri_app_handle: AppHandle) -> Vec<String> {
10331114
10341115#[ tauri:: command]
10351116pub async fn get_app_list ( tauri_app_handle : AppHandle ) -> Result < Vec < Extension > , String > {
1117+ use crate :: util:: app_lang:: { Lang , get_app_lang} ;
1118+
10361119 let search_paths = get_app_search_path ( tauri_app_handle. clone ( ) ) . await ;
10371120 let apps = list_app_in ( search_paths) ?;
10381121
10391122 let mut app_entries = Vec :: with_capacity ( apps. len ( ) ) ;
1123+ let lang = get_app_lang ( ) . await ;
10401124
10411125 for app in apps {
1042- let name = get_app_name ( & app) . await ;
1126+ let name = match lang {
1127+ Lang :: zh_CN => get_app_name_zh ( & app) . await ,
1128+ Lang :: en_US => get_app_name_en ( & app) . await ,
1129+ } ;
10431130
10441131 // filter out Coco-AI
1132+ //
1133+ // Coco does not have localized app names, so regardless the chosen language, name
1134+ // should have value "Coco-AI".
10451135 if name. eq ( & tauri_app_handle. package_info ( ) . name ) {
10461136 continue ;
10471137 }
0 commit comments