11import { bucketTypeSchema , I18nConfig , localeCodeSchema } from '@replexica/spec' ;
22import { ReplexicaEngine } from '@replexica/sdk' ;
33import { Command } from 'commander' ;
4- import Z from 'zod' ;
4+ import Z , { any } from 'zod' ;
55import _ from 'lodash' ;
66import { getConfig } from '../utils/config' ;
77import { getSettings } from '../utils/settings' ;
@@ -22,117 +22,134 @@ export default new Command()
2222 . option ( '--force' , 'Ignore lockfile and process all keys' )
2323 . option ( '--verbose' , 'Show verbose output' )
2424 . option ( '--api-key <api-key>' , 'Explicitly set the API key to use' )
25+ . option ( '--strict' , 'Stop on first error' )
2526 . action ( async function ( options ) {
2627 const ora = Ora ( ) ;
27-
28+ const results : any = [ ] ;
29+ const flags = parseFlags ( options ) ;
30+
2831 try {
2932 ora . start ( 'Loading configuration...' ) ;
30- const flags = parseFlags ( options ) ;
3133 const i18nConfig = getConfig ( ) ;
3234 const settings = getSettings ( flags . apiKey ) ;
3335 ora . succeed ( 'Configuration loaded' ) ;
36+
37+ try {
38+ ora . start ( 'Validating localization configuration...' ) ;
39+ validateParams ( i18nConfig , flags ) ;
40+ ora . succeed ( 'Localization configuration is valid' ) ;
41+ } catch ( error :any ) {
42+ handleWarning ( 'Localization configuration validation failed' , error , true , results ) ;
43+ return ;
44+ }
3445
35- ora . start ( 'Validating localization configuration...' ) ;
36- validateParams ( i18nConfig , flags ) ;
37- ora . succeed ( 'Localization configuration is valid' ) ;
38-
39- ora . start ( 'Connecting to Replexica Localization Engine...' ) ;
40- const auth = await validateAuth ( settings ) ;
41- ora . succeed ( 'Replexica Localization Engine connected' ) ;
42- ora . succeed ( `Authenticated as ${ auth . email } ` ) ;
46+ try {
47+ ora . start ( 'Connecting to Replexica Localization Engine...' ) ;
48+ const auth = await validateAuth ( settings ) ;
49+ ora . succeed ( `Authenticated as ${ auth . email } ` ) ;
50+ } catch ( error : any ) {
51+ handleWarning ( 'Failed to connect to Replexica Localization Engine' , error , true , results ) ;
52+ return ;
53+ }
4354
44- let buckets = getBuckets ( i18nConfig ! ) ;
45- if ( flags . bucket ) {
46- buckets = buckets . filter ( ( bucket ) => bucket . type === flags . bucket ) ;
55+ let buckets :any = [ ] ;
56+ try {
57+ buckets = getBuckets ( i18nConfig ! ) ;
58+ if ( flags . bucket ) {
59+ buckets = buckets . filter ( ( bucket :any ) => bucket . type === flags . bucket ) ;
60+ }
61+ ora . succeed ( 'Buckets retrieved' ) ;
62+ } catch ( error :any ) {
63+ handleWarning ( 'Failed to retrieve buckets' , error , true , results ) ;
64+ return ;
4765 }
4866
4967 const targetLocales = getTargetLocales ( i18nConfig ! , flags ) ;
5068 const lockfileHelper = createLockfileHelper ( ) ;
5169
5270 // Ensure the lockfile exists
53- ora . start ( 'Ensuring i18n.lock exists...' ) ;
54- if ( ! lockfileHelper . isLockfileExists ( ) ) {
55- ora . start ( 'Creating i18n.lock...' ) ;
56- for ( const bucket of buckets ) {
57- for ( const pathPattern of bucket . pathPatterns ) {
58- const bucketLoader = createBucketLoader ( bucket . type , pathPattern ) ;
59- bucketLoader . setDefaultLocale ( i18nConfig ! . locale . source ) ;
60-
61- const sourceData = await bucketLoader . pull ( i18nConfig ! . locale . source ) ;
62- lockfileHelper . registerSourceData ( pathPattern , sourceData ) ;
71+ try {
72+ ora . start ( 'Ensuring i18n.lock exists...' ) ;
73+ if ( ! lockfileHelper . isLockfileExists ( ) ) {
74+ ora . start ( 'Creating i18n.lock...' ) ;
75+ for ( const bucket of buckets ) {
76+ for ( const pathPattern of bucket . pathPatterns ) {
77+ const bucketLoader = createBucketLoader ( bucket . type , pathPattern ) ;
78+ bucketLoader . setDefaultLocale ( i18nConfig ! . locale . source ) ;
79+
80+ const sourceData = await bucketLoader . pull ( i18nConfig ! . locale . source ) ;
81+ lockfileHelper . registerSourceData ( pathPattern , sourceData ) ;
82+ }
6383 }
84+ ora . succeed ( 'i18n.lock created' ) ;
85+ } else {
86+ ora . succeed ( 'i18n.lock loaded' ) ;
6487 }
65- ora . succeed ( 'i18n.lock created' ) ;
66- } else {
67- ora . succeed ( 'i18n.lock loaded' ) ;
88+ } catch ( error : any ) {
89+ handleWarning ( 'Failed to ensure i18n.lock existence' , error , true , results ) ;
90+ return ;
6891 }
6992
70- // Exit with error if frozen flag is provided and there are any updated keys
71- if ( flags . frozen ) {
72- for ( const bucket of buckets ) {
93+ // Process each bucket
94+ for ( const bucket of buckets ) {
95+ try {
96+ console . log ( ) ;
97+ ora . info ( `Processing bucket: ${ bucket . type } ` ) ;
7398 for ( const pathPattern of bucket . pathPatterns ) {
99+ const bucketOra = Ora ( { indent : 2 } ) . info ( `Processing path: ${ pathPattern } ` ) ;
74100 const bucketLoader = createBucketLoader ( bucket . type , pathPattern ) ;
75101 bucketLoader . setDefaultLocale ( i18nConfig ! . locale . source ) ;
76102
77103 const sourceData = await bucketLoader . pull ( i18nConfig ! . locale . source ) ;
78- const updatedSourceData = lockfileHelper . extractUpdatedData ( pathPattern , sourceData ) ;
79- if ( Object . keys ( updatedSourceData ) . length ) {
80- throw new ReplexicaCLIError ( {
81- message : `Translations are not up to date. Run the command without the --frozen flag to update the translations, then try again.` ,
82- docUrl : "translationFailed"
83- } ) ;
84- }
85- }
86- }
87- }
88- // Process each bucket
89- for ( const bucket of buckets ) {
90- console . log ( ) ;
91- ora . info ( `Processing bucket: ${ bucket . type } ` ) ;
92- for ( const pathPattern of bucket . pathPatterns ) {
93- const bucketOra = Ora ( { indent : 2 } ) . info ( `Processing path: ${ pathPattern } ` ) ;
94-
95- const bucketLoader = createBucketLoader ( bucket . type , pathPattern ) ;
96- bucketLoader . setDefaultLocale ( i18nConfig ! . locale . source ) ;
97-
98- const sourceData = await bucketLoader . pull ( i18nConfig ! . locale . source ) ;
99- const updatedSourceData = flags . force ? sourceData : lockfileHelper . extractUpdatedData ( pathPattern , sourceData ) ;
100-
101- for ( const targetLocale of targetLocales ) {
102- bucketOra . start ( `[${ i18nConfig ! . locale . source } -> ${ targetLocale } ] AI localization in progress...` ) ;
103-
104- const targetData = await bucketLoader . pull ( targetLocale ) ;
105-
106- const processableData = calculateDataDelta ( { sourceData, updatedSourceData, targetData } ) ;
107- if ( flags . verbose ) {
108- bucketOra . info ( JSON . stringify ( processableData , null , 2 ) ) ;
109- }
110-
111- const localizationEngine = createLocalizationEngineConnection ( {
112- apiKey : settings . auth . apiKey ,
113- apiUrl : settings . auth . apiUrl ,
114- } ) ;
115- const processedTargetData = await localizationEngine . process ( {
116- sourceLocale : i18nConfig ! . locale . source ,
117- sourceData,
118- processableData,
119- targetLocale,
120- targetData,
121- } , ( progress ) => {
122- bucketOra . text = `[${ i18nConfig ! . locale . source } -> ${ targetLocale } ] (${ progress } %) AI localization in progress...` ;
123- } ) ;
124-
125- if ( flags . verbose ) {
126- bucketOra . info ( JSON . stringify ( processedTargetData , null , 2 ) ) ;
104+ const updatedSourceData = flags . force ? sourceData : lockfileHelper . extractUpdatedData ( pathPattern , sourceData ) ;
105+
106+ for ( const targetLocale of targetLocales ) {
107+ try {
108+ bucketOra . start ( `[${ i18nConfig ! . locale . source } -> ${ targetLocale } ] AI localization in progress...` ) ;
109+
110+ const targetData = await bucketLoader . pull ( targetLocale ) ;
111+ const processableData = calculateDataDelta ( { sourceData, updatedSourceData, targetData } ) ;
112+ if ( flags . verbose ) {
113+ bucketOra . info ( JSON . stringify ( processableData , null , 2 ) ) ;
114+ }
115+
116+ const localizationEngine = createLocalizationEngineConnection ( {
117+ apiKey : settings . auth . apiKey ,
118+ apiUrl : settings . auth . apiUrl ,
119+ } ) ;
120+ let processedTargetData ;
121+ try {
122+ processedTargetData = await localizationEngine . process ( {
123+ sourceLocale : i18nConfig ! . locale . source ,
124+ sourceData,
125+ processableData,
126+ targetLocale,
127+ targetData,
128+ } , ( progress ) => {
129+ bucketOra . text = `[${ i18nConfig ! . locale . source } -> ${ targetLocale } ] (${ progress } %) AI localization in progress...` ;
130+ } ) ;
131+
132+ } catch ( error : any ) {
133+ handleWarning ( 'Failed to process target data' , error , flags . strict , results ) ;
134+ if ( flags . strict ) return ;
135+ }
136+
137+ if ( flags . verbose ) {
138+ bucketOra . info ( JSON . stringify ( processedTargetData , null , 2 ) ) ;
139+ }
140+ const finalTargetData = _ . merge ( { } , sourceData , targetData , processedTargetData ) ;
141+ await bucketLoader . push ( targetLocale , finalTargetData ) ;
142+ bucketOra . succeed ( `[${ i18nConfig ! . locale . source } -> ${ targetLocale } ] AI localization completed` ) ;
143+ } catch ( error :any ) {
144+ handleWarning ( `Failed to localize for ${ targetLocale } ` , error , flags . strict , results ) ;
145+ if ( flags . strict ) return ;
146+ }
127147 }
128- const finalTargetData = _ . merge ( { } , sourceData , targetData , processedTargetData ) ;
129-
130- await bucketLoader . push ( targetLocale , finalTargetData ) ;
131-
132- bucketOra . succeed ( `[${ i18nConfig ! . locale . source } -> ${ targetLocale } ] AI localization completed` ) ;
148+ lockfileHelper . registerSourceData ( pathPattern , sourceData ) ;
133149 }
134-
135- lockfileHelper . registerSourceData ( pathPattern , sourceData ) ;
150+ } catch ( error :any ) {
151+ handleWarning ( `Failed to process bucket: ${ bucket . type } ` , error , flags . strict , results ) ;
152+ if ( flags . strict ) return ;
136153 }
137154 }
138155
@@ -141,10 +158,26 @@ export default new Command()
141158 } catch ( error : any ) {
142159 ora . fail ( error . message ) ;
143160 process . exit ( 1 ) ;
161+ } finally {
162+ displaySummary ( results ) ;
144163 }
145164 } ) ;
146165
147166
167+ function handleWarning ( step : string , error : Error , strictMode : boolean | undefined , results : any [ ] ) {
168+ console . warn ( `[WARNING] ${ step } : ${ error . message } ` ) ;
169+ results . push ( { step, status : "Failed" , error : error . message } ) ;
170+ if ( strictMode ) throw error ;
171+ }
172+
173+ function displaySummary ( results : any [ ] ) {
174+ console . log ( "\nProcess Summary:" ) ;
175+ results . forEach ( ( result ) => {
176+ console . log ( `${ result . step } : ${ result . status } ` ) ;
177+ if ( result . error ) console . log ( ` - Error: ${ result . error } ` ) ;
178+ } ) ;
179+ }
180+
148181function calculateDataDelta ( args : {
149182 sourceData : Record < string , any > ;
150183 updatedSourceData : Record < string , any > ;
@@ -210,6 +243,7 @@ function parseFlags(options: any) {
210243 force : Z . boolean ( ) . optional ( ) ,
211244 frozen : Z . boolean ( ) . optional ( ) ,
212245 verbose : Z . boolean ( ) . optional ( ) ,
246+ strict : Z . boolean ( ) . optional ( ) ,
213247 } ) . parse ( options ) ;
214248}
215249
0 commit comments