1- use langcodec:: { Codec , types:: EntryStatus } ;
2- use std:: collections:: { BTreeMap , HashSet } ;
1+ use langcodec:: {
2+ Codec ,
3+ types:: { EntryStatus , PluralCategory , Translation } ,
4+ } ;
5+ use serde_json:: { Map , Value , json} ;
6+ use std:: collections:: { BTreeMap , BTreeSet , HashSet } ;
37
48pub struct ViewOptions {
59 pub full : bool ,
@@ -59,10 +63,125 @@ fn truncate_chars(s: &str, max_chars: usize) -> String {
5963 }
6064}
6165
66+ fn status_label ( status : & EntryStatus ) -> & ' static str {
67+ match status {
68+ EntryStatus :: DoNotTranslate => "do_not_translate" ,
69+ EntryStatus :: New => "new" ,
70+ EntryStatus :: Stale => "stale" ,
71+ EntryStatus :: NeedsReview => "needs_review" ,
72+ EntryStatus :: Translated => "translated" ,
73+ }
74+ }
75+
76+ fn plural_category_label ( category : & PluralCategory ) -> & ' static str {
77+ match category {
78+ PluralCategory :: Zero => "zero" ,
79+ PluralCategory :: One => "one" ,
80+ PluralCategory :: Two => "two" ,
81+ PluralCategory :: Few => "few" ,
82+ PluralCategory :: Many => "many" ,
83+ PluralCategory :: Other => "other" ,
84+ }
85+ }
86+
87+ fn render_json_output (
88+ filtered_resources : & [ ( & langcodec:: Resource , Vec < & langcodec:: types:: Entry > ) ] ,
89+ lang_filter : & Option < String > ,
90+ keys_only : bool ,
91+ ) -> Result < String , String > {
92+ let mut total_matches = 0usize ;
93+ let mut languages = BTreeSet :: new ( ) ;
94+ let mut status_counts: BTreeMap < String , usize > = BTreeMap :: new ( ) ;
95+ let mut entries_payload = Vec :: new ( ) ;
96+ let mut keys_by_language: BTreeMap < String , Vec < String > > = BTreeMap :: new ( ) ;
97+ let mut keys_for_lang = Vec :: new ( ) ;
98+
99+ for ( resource, entries) in filtered_resources {
100+ languages. insert ( resource. metadata . language . clone ( ) ) ;
101+
102+ for entry in entries {
103+ total_matches += 1 ;
104+ let status = status_label ( & entry. status ) . to_string ( ) ;
105+ * status_counts. entry ( status. clone ( ) ) . or_insert ( 0 ) += 1 ;
106+
107+ if keys_only {
108+ if lang_filter. is_some ( ) {
109+ keys_for_lang. push ( entry. id . clone ( ) ) ;
110+ } else {
111+ keys_by_language
112+ . entry ( resource. metadata . language . clone ( ) )
113+ . or_default ( )
114+ . push ( entry. id . clone ( ) ) ;
115+ }
116+ continue ;
117+ }
118+
119+ let mut entry_json = Map :: new ( ) ;
120+ entry_json. insert ( "lang" . to_string ( ) , json ! ( resource. metadata. language) ) ;
121+ entry_json. insert ( "key" . to_string ( ) , json ! ( entry. id) ) ;
122+ entry_json. insert ( "status" . to_string ( ) , json ! ( status) ) ;
123+ entry_json. insert ( "domain" . to_string ( ) , json ! ( resource. metadata. domain) ) ;
124+
125+ match & entry. value {
126+ Translation :: Empty => {
127+ entry_json. insert ( "type" . to_string ( ) , json ! ( "empty" ) ) ;
128+ }
129+ Translation :: Singular ( value) => {
130+ entry_json. insert ( "type" . to_string ( ) , json ! ( "singular" ) ) ;
131+ entry_json. insert ( "value" . to_string ( ) , json ! ( value) ) ;
132+ }
133+ Translation :: Plural ( plural) => {
134+ entry_json. insert ( "type" . to_string ( ) , json ! ( "plural" ) ) ;
135+ entry_json. insert ( "plural_id" . to_string ( ) , json ! ( plural. id) ) ;
136+ let mut forms = Map :: new ( ) ;
137+ for ( category, value) in & plural. forms {
138+ forms. insert ( plural_category_label ( category) . to_string ( ) , json ! ( value) ) ;
139+ }
140+ entry_json. insert ( "forms" . to_string ( ) , Value :: Object ( forms) ) ;
141+ }
142+ }
143+
144+ if let Some ( comment) = & entry. comment {
145+ entry_json. insert ( "comment" . to_string ( ) , json ! ( comment) ) ;
146+ }
147+
148+ entries_payload. push ( Value :: Object ( entry_json) ) ;
149+ }
150+ }
151+
152+ let summary = json ! ( {
153+ "total_matches" : total_matches,
154+ "languages" : languages. into_iter( ) . collect:: <Vec <_>>( ) ,
155+ "statuses" : status_counts,
156+ } ) ;
157+
158+ let payload = if keys_only {
159+ if lang_filter. is_some ( ) {
160+ json ! ( {
161+ "summary" : summary,
162+ "keys" : keys_for_lang,
163+ } )
164+ } else {
165+ json ! ( {
166+ "summary" : summary,
167+ "keys" : keys_by_language,
168+ } )
169+ }
170+ } else {
171+ json ! ( {
172+ "summary" : summary,
173+ "entries" : entries_payload,
174+ } )
175+ } ;
176+
177+ serde_json:: to_string_pretty ( & payload)
178+ . map_err ( |e| format ! ( "Failed to render view JSON payload: {e}" ) )
179+ }
180+
62181/// Print a view of the resources in a codec.
63182pub fn print_view ( codec : & Codec , lang_filter : & Option < String > , opts : & ViewOptions ) {
64183 let keys_only_text = opts. keys_only && !opts. json ;
65- if !keys_only_text {
184+ if !keys_only_text && !opts . json {
66185 println ! ( "Processing resources..." ) ;
67186 }
68187 let status_filter = match parse_status_filter ( & opts. status ) {
@@ -107,7 +226,7 @@ pub fn print_view(codec: &Codec, lang_filter: &Option<String>, opts: &ViewOption
107226 std:: process:: exit ( 1 ) ;
108227 }
109228
110- if !keys_only_text {
229+ if !keys_only_text && !opts . json {
111230 println ! ( "✅ Found {} resource(s)" , resources. len( ) ) ;
112231 }
113232
@@ -127,6 +246,18 @@ pub fn print_view(codec: &Codec, lang_filter: &Option<String>, opts: &ViewOption
127246 } )
128247 . collect :: < Vec < _ > > ( ) ;
129248
249+ if opts. json {
250+ let rendered = match render_json_output ( & filtered_resources, lang_filter, opts. keys_only ) {
251+ Ok ( text) => text,
252+ Err ( err) => {
253+ eprintln ! ( "❌ {}" , err) ;
254+ std:: process:: exit ( 1 ) ;
255+ }
256+ } ;
257+ println ! ( "{}" , rendered) ;
258+ return ;
259+ }
260+
130261 if keys_only_text {
131262 let include_lang_prefix = lang_filter. is_none ( ) ;
132263 for ( resource, entries) in & filtered_resources {
@@ -156,10 +287,10 @@ pub fn print_view(codec: &Codec, lang_filter: &Option<String>, opts: &ViewOption
156287 }
157288
158289 match & entry. value {
159- langcodec :: types :: Translation :: Empty => {
290+ Translation :: Empty => {
160291 println ! ( " Type: Empty" ) ;
161292 }
162- langcodec :: types :: Translation :: Singular ( value) => {
293+ Translation :: Singular ( value) => {
163294 println ! ( " Type: Singular" ) ;
164295 if opts. full {
165296 println ! ( " Value: {}" , value) ;
@@ -168,7 +299,7 @@ pub fn print_view(codec: &Codec, lang_filter: &Option<String>, opts: &ViewOption
168299 println ! ( " Value: {}" , truncated) ;
169300 }
170301 }
171- langcodec :: types :: Translation :: Plural ( plural) => {
302+ Translation :: Plural ( plural) => {
172303 println ! ( " Type: Plural" ) ;
173304 println ! ( " Plural ID: {}" , plural. id) ;
174305 for ( category, value) in & plural. forms {
0 commit comments