@@ -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