@easyling/sanity-auto-translate 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,748 @@
1
+ # Sanity Function: Auto-Translate on Publish
2
+
3
+ Serverless function that automatically translates Sanity documents when published. Hooks into the `document.publish` lifecycle event to create translated versions without blocking the publish operation.
4
+
5
+ ## Features
6
+
7
+ - **Automatic Translation**: Translates documents on publish without manual intervention
8
+ - **Configurable Document Types**: Control which document types are auto-translated
9
+ - **Multi-Locale Support**: Translate to multiple target languages simultaneously
10
+ - **Shared Configuration**: Uses the same configuration as the Studio plugin (no environment variables needed)
11
+ - **Non-Blocking**: Never blocks document publishing - errors are logged and gracefully handled
12
+ - **Deadline Monitoring**: Tracks execution time and terminates gracefully before timeout
13
+ - **Size Limit Protection**: Skips documents exceeding size limits to prevent timeouts
14
+ - **Comprehensive Logging**: Structured JSON logs for monitoring and debugging
15
+ - **Metrics Tracking**: Tracks success/failure rates per locale and error types
16
+
17
+ ## How It Works
18
+
19
+ 1. **Trigger**: Function is invoked when any document is published in Sanity
20
+ 2. **Filter**: Checks if document type is in `autoTranslateDocumentTypes` configuration
21
+ 3. **Extract**: Uses `ContentExtractor` from shared library to extract translatable content
22
+ 4. **Translate**: Sends translation request to configured API using `TranslationService`
23
+ 5. **Create**: Creates translated documents as drafts using `DocumentCreationService`
24
+ 6. **Log**: Records all operations with structured logging for monitoring
25
+
26
+ The function uses the same core services as the Studio plugin, ensuring identical translation behavior across both contexts.
27
+
28
+ ## Deployment
29
+
30
+ ### Prerequisites
31
+
32
+ - Sanity CLI installed: `npm install -g @sanity/cli`
33
+ - Sanity project with dataset
34
+ - Write access to the Sanity project
35
+
36
+ ### Deploy the Function
37
+
38
+ 1. Navigate to the function directory:
39
+
40
+ ```bash
41
+ cd function
42
+ ```
43
+
44
+ 2. Build the function:
45
+
46
+ ```bash
47
+ npm run build
48
+ ```
49
+
50
+ 3. Deploy using Sanity CLI:
51
+
52
+ ```bash
53
+ sanity function deploy
54
+ ```
55
+
56
+ The CLI will:
57
+ - Upload the function code to Sanity's infrastructure
58
+ - Register the `document.publish` trigger
59
+ - Set the 120-second execution timeout
60
+ - Activate the function for your project
61
+
62
+ ### Verify Deployment
63
+
64
+ Check function status:
65
+
66
+ ```bash
67
+ sanity function list
68
+ ```
69
+
70
+ View function logs:
71
+
72
+ ```bash
73
+ sanity function logs translate-on-publish
74
+ ```
75
+
76
+ ## Configuration
77
+
78
+ The function reads all configuration from your Sanity dataset using the `EL_PluginConfiguration` document. This is the same configuration used by the Studio plugin, providing a single source of truth.
79
+
80
+ ### Configuration Document
81
+
82
+ - **Document ID**: `sanity-translation-plugin.config`
83
+ - **Document Type**: `EL_PluginConfiguration`
84
+
85
+ ### Required Configuration Fields
86
+
87
+ #### OAuth Credentials
88
+
89
+ The function requires OAuth credentials to authenticate with the translation service:
90
+
91
+ - `projectId`: Your translation project identifier
92
+ - `accessToken`: Long-lived access token for API authentication
93
+
94
+ Configure through the Studio plugin UI or directly in the configuration document.
95
+
96
+ #### Target Locales
97
+
98
+ Define which languages to translate to:
99
+
100
+ ```typescript
101
+ {
102
+ locales: [
103
+ {
104
+ code: 'en',
105
+ title: 'English',
106
+ enabled: true,
107
+ isDefault: true // Source language
108
+ },
109
+ {
110
+ code: 'ja',
111
+ title: 'Japanese',
112
+ enabled: true,
113
+ isDefault: false
114
+ },
115
+ {
116
+ code: 'de',
117
+ title: 'German',
118
+ enabled: true,
119
+ isDefault: false
120
+ }
121
+ ]
122
+ }
123
+ ```
124
+
125
+ Only locales with `enabled: true` will be used as translation targets. The locale with `isDefault: true` is used as the source language.
126
+
127
+ #### Auto-Translate Document Types
128
+
129
+ Specify which document types should be automatically translated:
130
+
131
+ ```typescript
132
+ {
133
+ autoTranslateDocumentTypes: ['article', 'blogPost', 'page']
134
+ }
135
+ ```
136
+
137
+ **Important**: Only document types in this array will trigger automatic translation. This prevents unwanted translations of configuration documents, user profiles, etc.
138
+
139
+ ### Optional Configuration Fields
140
+
141
+ #### Translation API Endpoint
142
+
143
+ Customize the translation service endpoint:
144
+
145
+ ```typescript
146
+ {
147
+ translationApiEndpoint: 'https://api.easyling.com/translate'
148
+ }
149
+ ```
150
+
151
+ **Default**: Easyling translation API endpoint
152
+
153
+ #### Content Type Settings
154
+
155
+ Configure request/response format:
156
+
157
+ ```typescript
158
+ {
159
+ requestContentType: 'application/x-protobuf', // or 'application/json'
160
+ responseAcceptHeader: 'application/x-protobuf' // or 'application/json'
161
+ }
162
+ ```
163
+
164
+ **Default**: `application/json` for both
165
+
166
+ #### Document Creation Mode
167
+
168
+ Control how translated documents are created:
169
+
170
+ ```typescript
171
+ {
172
+ defaultDocumentCreationMode: 'draft' // or 'published'
173
+ }
174
+ ```
175
+
176
+ **Default**: `draft` (recommended to allow review before publishing)
177
+
178
+ ### Configuration Example
179
+
180
+ Complete configuration document:
181
+
182
+ ```json
183
+ {
184
+ "_id": "sanity-translation-plugin.config",
185
+ "_type": "EL_PluginConfiguration",
186
+ "projectId": "your-easyling-project-id",
187
+ "accessToken": "your-access-token",
188
+ "translationApiEndpoint": "https://app.easyling.com/_el/ext/direct/sanity",
189
+ "requestContentType": "application/json",
190
+ "responseAcceptHeader": "application/json",
191
+ "defaultDocumentCreationMode": "draft",
192
+ "autoTranslateDocumentTypes": ["article", "blogPost", "page"],
193
+ "locales": [
194
+ {
195
+ "code": "en",
196
+ "title": "English",
197
+ "enabled": true,
198
+ "isDefault": true
199
+ },
200
+ {
201
+ "code": "ja",
202
+ "title": "Japanese",
203
+ "enabled": true,
204
+ "isDefault": false
205
+ },
206
+ {
207
+ "code": "de",
208
+ "title": "German",
209
+ "enabled": true,
210
+ "isDefault": false
211
+ }
212
+ ]
213
+ }
214
+ ```
215
+
216
+ ## Blueprint Configuration
217
+
218
+ The function is configured via `sanity.function.json`:
219
+
220
+ ```json
221
+ {
222
+ "name": "translate-on-publish",
223
+ "description": "Automatically translate documents when published",
224
+ "runtime": "nodejs20",
225
+ "trigger": {
226
+ "type": "document.publish"
227
+ },
228
+ "timeout": 120
229
+ }
230
+ ```
231
+
232
+ ### Blueprint Fields
233
+
234
+ - **name**: Function identifier (used in CLI commands and logs)
235
+ - **description**: Human-readable description
236
+ - **runtime**: Node.js version (nodejs20)
237
+ - **trigger.type**: Lifecycle event (`document.publish`)
238
+ - **timeout**: Maximum execution time in seconds (120)
239
+
240
+ ### No Environment Variables Required
241
+
242
+ Unlike typical serverless functions, this function does not require environment variables. All configuration is read from the Sanity dataset, which provides several benefits:
243
+
244
+ - **Single Source of Truth**: Configuration is shared with Studio plugin
245
+ - **Easy Updates**: Change configuration through Studio UI without redeploying
246
+ - **No Secrets Management**: Credentials stored securely in Sanity dataset
247
+ - **Immediate Effect**: Configuration changes take effect on next function invocation
248
+
249
+ ## Monitoring and Logging
250
+
251
+ ### Structured Logs
252
+
253
+ The function emits structured JSON logs for easy parsing:
254
+
255
+ ```json
256
+ {
257
+ "timestamp": "2024-01-15T10:30:45.123Z",
258
+ "level": "info",
259
+ "message": "Translation workflow completed",
260
+ "context": {
261
+ "documentId": "article-123",
262
+ "documentType": "article",
263
+ "operation": "translateOnPublish",
264
+ "successCount": 2,
265
+ "failureCount": 0,
266
+ "elapsedMs": 3456
267
+ }
268
+ }
269
+ ```
270
+
271
+ ### Log Levels
272
+
273
+ - **info**: Normal operation events (function start, translation complete, etc.)
274
+ - **warn**: Non-critical issues (approaching deadline, document skipped, etc.)
275
+ - **error**: Failures that don't block publish (translation failed, document creation failed, etc.)
276
+
277
+ ### Key Log Events
278
+
279
+ #### Function Invocation
280
+
281
+ ```json
282
+ {
283
+ "level": "info",
284
+ "message": "Function invoked",
285
+ "context": {
286
+ "documentId": "article-123",
287
+ "documentType": "article",
288
+ "startTime": 1705315845123
289
+ }
290
+ }
291
+ ```
292
+
293
+ #### Configuration Loaded
294
+
295
+ ```json
296
+ {
297
+ "level": "info",
298
+ "message": "Configuration validated",
299
+ "context": {
300
+ "targetLocales": ["ja", "de"],
301
+ "autoTranslateDocumentTypes": ["article", "blogPost"]
302
+ }
303
+ }
304
+ ```
305
+
306
+ #### Document Skipped
307
+
308
+ ```json
309
+ {
310
+ "level": "info",
311
+ "message": "Document type not configured for auto-translation",
312
+ "context": {
313
+ "documentType": "user",
314
+ "configuredTypes": ["article", "blogPost"]
315
+ }
316
+ }
317
+ ```
318
+
319
+ #### Translation Completed
320
+
321
+ ```json
322
+ {
323
+ "level": "info",
324
+ "message": "Translation workflow completed",
325
+ "context": {
326
+ "totalResults": 2,
327
+ "successCount": 2,
328
+ "failureCount": 0,
329
+ "elapsedMs": 3456,
330
+ "metrics": {
331
+ "successfulTranslations": 2,
332
+ "failedTranslations": 0,
333
+ "localeMetrics": {
334
+ "ja": { "successCount": 1, "failureCount": 0 },
335
+ "de": { "successCount": 1, "failureCount": 0 }
336
+ }
337
+ }
338
+ }
339
+ }
340
+ ```
341
+
342
+ #### Error Occurred
343
+
344
+ ```json
345
+ {
346
+ "level": "error",
347
+ "message": "Translation service call failed",
348
+ "context": {
349
+ "documentId": "article-123",
350
+ "documentType": "article",
351
+ "targetLocales": ["ja", "de"],
352
+ "error": "Network timeout",
353
+ "stack": "Error: Network timeout\n at ..."
354
+ }
355
+ }
356
+ ```
357
+
358
+ ### Viewing Logs
359
+
360
+ View real-time logs:
361
+
362
+ ```bash
363
+ sanity function logs translate-on-publish --follow
364
+ ```
365
+
366
+ View recent logs:
367
+
368
+ ```bash
369
+ sanity function logs translate-on-publish --limit 100
370
+ ```
371
+
372
+ Filter by log level:
373
+
374
+ ```bash
375
+ sanity function logs translate-on-publish --level error
376
+ ```
377
+
378
+ ### Metrics Tracking
379
+
380
+ The function tracks comprehensive metrics for monitoring:
381
+
382
+ ```typescript
383
+ {
384
+ totalInvocations: 1,
385
+ successfulTranslations: 2,
386
+ failedTranslations: 0,
387
+ skippedTranslations: 0,
388
+ localeMetrics: {
389
+ "ja": { successCount: 1, failureCount: 0 },
390
+ "de": { successCount: 1, failureCount: 0 }
391
+ },
392
+ errorsByStage: {
393
+ configurationLoad: 0,
394
+ configurationValidation: 0,
395
+ contentExtraction: 0,
396
+ translationService: 0,
397
+ documentCreation: 0,
398
+ timeout: 0,
399
+ sizeLimit: 0,
400
+ other: 0
401
+ }
402
+ }
403
+ ```
404
+
405
+ Use these metrics to:
406
+ - Track translation success rates per locale
407
+ - Identify problematic document types
408
+ - Monitor timeout and size limit issues
409
+ - Detect configuration problems
410
+
411
+ ## Troubleshooting
412
+
413
+ ### Function Not Triggering
414
+
415
+ **Problem**: Documents are published but function doesn't run
416
+
417
+ **Solutions**:
418
+
419
+ 1. Verify function is deployed:
420
+ ```bash
421
+ sanity function list
422
+ ```
423
+
424
+ 2. Check function status is "active"
425
+
426
+ 3. Verify document type is in `autoTranslateDocumentTypes`:
427
+ ```bash
428
+ sanity function logs translate-on-publish --limit 10
429
+ ```
430
+ Look for "Document type not configured for auto-translation" messages
431
+
432
+ 4. Check configuration document exists:
433
+ - Open Studio
434
+ - Navigate to `EL_PluginConfiguration` document
435
+ - Verify `autoTranslateDocumentTypes` array is populated
436
+
437
+ ### Translation Fails with Authentication Error
438
+
439
+ **Problem**: Function logs show 401 or 403 errors
440
+
441
+ **Solutions**:
442
+
443
+ 1. Verify OAuth credentials in configuration:
444
+ - Check `projectId` is correct
445
+ - Check `accessToken` is valid and not expired
446
+
447
+ 2. Test credentials with Studio plugin:
448
+ - Try manual translation in Studio
449
+ - If Studio works, function should work too
450
+
451
+ 3. Check translation API endpoint:
452
+ - Verify `translationApiEndpoint` is correct
453
+ - Ensure endpoint is accessible from Sanity's infrastructure
454
+
455
+ 4. Contact translation service provider:
456
+ - Request new access token
457
+ - Verify project ID is active
458
+
459
+ ### Documents Not Created
460
+
461
+ **Problem**: Translation succeeds but no documents appear
462
+
463
+ **Solutions**:
464
+
465
+ 1. Check function logs for document creation errors:
466
+ ```bash
467
+ sanity function logs translate-on-publish --level error
468
+ ```
469
+
470
+ 2. Verify creation mode:
471
+ - If `defaultDocumentCreationMode` is "draft", check Drafts view
472
+ - If "published", check published documents
473
+
474
+ 3. Check for collision resolution:
475
+ - Function may skip creation if document already exists
476
+ - Look for "Collision detected" in logs
477
+
478
+ 4. Verify Sanity client permissions:
479
+ - Function needs write access to dataset
480
+ - Check project permissions in Sanity dashboard
481
+
482
+ ### Function Timeout
483
+
484
+ **Problem**: Function times out before completing
485
+
486
+ **Solutions**:
487
+
488
+ 1. Check document size:
489
+ - Function skips documents over 10MB
490
+ - Look for "Document exceeds size limit" in logs
491
+
492
+ 2. Reduce target locales:
493
+ - Translating to many locales increases execution time
494
+ - Consider translating to fewer locales per publish
495
+
496
+ 3. Check translation API response time:
497
+ - Slow API responses can cause timeouts
498
+ - Monitor "elapsedMs" in logs
499
+
500
+ 4. Review deadline warnings:
501
+ - Function logs warnings at 100 seconds
502
+ - If consistently hitting warnings, optimize document size or locale count
503
+
504
+ ### Configuration Not Loading
505
+
506
+ **Problem**: Function logs show "Configuration not found"
507
+
508
+ **Solutions**:
509
+
510
+ 1. Verify configuration document exists:
511
+ ```bash
512
+ sanity documents get sanity-translation-plugin.config
513
+ ```
514
+
515
+ 2. Check document type:
516
+ - Must be `EL_PluginConfiguration`
517
+ - Must have document ID `sanity-translation-plugin.config`
518
+
519
+ 3. Create configuration through Studio plugin:
520
+ - Install and configure Studio plugin first
521
+ - Plugin will create configuration document
522
+
523
+ 4. Manually create configuration document:
524
+ ```bash
525
+ sanity documents create --replace <<EOF
526
+ {
527
+ "_id": "sanity-translation-plugin.config",
528
+ "_type": "EL_PluginConfiguration",
529
+ "projectId": "your-project-id",
530
+ "accessToken": "your-token",
531
+ "autoTranslateDocumentTypes": ["article"],
532
+ "locales": [
533
+ {"code": "en", "title": "English", "enabled": true, "isDefault": true},
534
+ {"code": "ja", "title": "Japanese", "enabled": true, "isDefault": false}
535
+ ]
536
+ }
537
+ EOF
538
+ ```
539
+
540
+ ### Partial Translations
541
+
542
+ **Problem**: Some locales succeed, others fail
543
+
544
+ **Solutions**:
545
+
546
+ 1. Check locale-specific metrics in logs:
547
+ ```json
548
+ {
549
+ "localeMetrics": {
550
+ "ja": { "successCount": 1, "failureCount": 0 },
551
+ "de": { "successCount": 0, "failureCount": 1 }
552
+ }
553
+ }
554
+ ```
555
+
556
+ 2. Review error messages for failed locales:
557
+ - Look for locale-specific errors in logs
558
+ - May indicate API issues for specific languages
559
+
560
+ 3. Verify locale codes:
561
+ - Ensure locale codes match translation service expectations
562
+ - Check for typos in locale configuration
563
+
564
+ 4. Test individual locales:
565
+ - Temporarily disable problematic locales
566
+ - Test with Studio plugin to isolate issue
567
+
568
+ ### High Error Rate
569
+
570
+ **Problem**: Many translations failing
571
+
572
+ **Solutions**:
573
+
574
+ 1. Review error breakdown by stage:
575
+ ```json
576
+ {
577
+ "errorsByStage": {
578
+ "contentExtraction": 5,
579
+ "translationService": 12,
580
+ "documentCreation": 3
581
+ }
582
+ }
583
+ ```
584
+
585
+ 2. Address most common error stage:
586
+ - **contentExtraction**: Document structure issues, invalid portable text
587
+ - **translationService**: API issues, authentication, network problems
588
+ - **documentCreation**: Permission issues, validation errors, collisions
589
+
590
+ 3. Check for systematic issues:
591
+ - Specific document type causing problems
592
+ - Specific locale consistently failing
593
+ - Time-based patterns (API rate limits, downtime)
594
+
595
+ 4. Enable debug mode in Studio plugin:
596
+ - Set `debugMode: true` in configuration
597
+ - Review detailed field information
598
+ - Identify problematic fields or content
599
+
600
+ ## Performance Considerations
601
+
602
+ ### Execution Time
603
+
604
+ - **Typical**: 3-5 seconds per document with 2-3 target locales
605
+ - **Maximum**: 120 seconds (hard timeout)
606
+ - **Warning Threshold**: 100 seconds (function logs warning)
607
+ - **Termination Threshold**: 110 seconds (function terminates gracefully)
608
+
609
+ ### Document Size Limits
610
+
611
+ - **Maximum**: 10MB per document
612
+ - Documents exceeding limit are skipped with warning log
613
+ - Consider splitting large documents into smaller pieces
614
+
615
+ ### Concurrent Translations
616
+
617
+ The function processes target locales sequentially to:
618
+ - Avoid overwhelming translation API
619
+ - Provide better error isolation
620
+ - Enable partial success (some locales succeed even if others fail)
621
+
622
+ ### Optimization Tips
623
+
624
+ 1. **Limit Target Locales**: Fewer locales = faster execution
625
+ 2. **Use Draft Mode**: Creating drafts is faster than published documents
626
+ 3. **Optimize Document Structure**: Simpler structures extract faster
627
+ 4. **Monitor Execution Time**: Review "elapsedMs" in logs to identify slow documents
628
+ 5. **Batch Similar Documents**: Publish related documents together for better caching
629
+
630
+ ## Shared Configuration with Studio Plugin
631
+
632
+ The function and Studio plugin share the same configuration document, providing several benefits:
633
+
634
+ ### Single Source of Truth
635
+
636
+ - No duplicate configuration to maintain
637
+ - Changes apply to both function and plugin
638
+ - Consistent behavior across manual and automatic translation
639
+
640
+ ### Easy Management
641
+
642
+ - Configure through Studio UI (user-friendly)
643
+ - Or edit configuration document directly (programmatic)
644
+ - No need to redeploy function after configuration changes
645
+
646
+ ### Configuration Fields Used by Function
647
+
648
+ The function reads these fields from `EL_PluginConfiguration`:
649
+
650
+ - `projectId`: OAuth project identifier
651
+ - `accessToken`: OAuth access token
652
+ - `translationApiEndpoint`: Translation API URL
653
+ - `requestContentType`: Request format (JSON or protobuf)
654
+ - `responseAcceptHeader`: Response format (JSON or protobuf)
655
+ - `defaultDocumentCreationMode`: Draft or published
656
+ - `autoTranslateDocumentTypes`: Document types to auto-translate
657
+ - `locales`: Locale definitions with enabled flags
658
+ - `defaultLocale`: Source language code
659
+
660
+ ### Configuration Fields Ignored by Function
661
+
662
+ These fields are used only by the Studio plugin:
663
+
664
+ - `collisionResolutionMode`: UI-specific collision handling
665
+ - `existingDocumentHandling`: UI-specific document handling
666
+ - `dntFieldConfigurations`: DNT preferences (function uses defaults)
667
+ - `debugMode`: UI-specific debug display
668
+
669
+ ## Development
670
+
671
+ ### Local Testing
672
+
673
+ Test the function locally before deploying:
674
+
675
+ ```bash
676
+ # Build the function
677
+ npm run build
678
+
679
+ # Run tests
680
+ npm test
681
+
682
+ # Run specific test file
683
+ npm test -- config.test.ts
684
+ ```
685
+
686
+ ### Function Structure
687
+
688
+ ```
689
+ function/
690
+ ├── src/
691
+ │ ├── index.ts # Main function entry point
692
+ │ ├── config.ts # Configuration loading and validation
693
+ │ ├── logger.ts # Structured logging
694
+ │ └── __tests__/ # Unit and integration tests
695
+ ├── sanity.function.json # Blueprint configuration
696
+ ├── package.json # Dependencies and scripts
697
+ └── tsconfig.json # TypeScript configuration
698
+ ```
699
+
700
+ ### Key Files
701
+
702
+ - **index.ts**: Main function logic, handles publish events
703
+ - **config.ts**: Loads configuration from Sanity dataset
704
+ - **logger.ts**: Structured logging with context
705
+ - **sanity.function.json**: Deployment configuration
706
+
707
+ ### Testing
708
+
709
+ ```bash
710
+ # Run all tests
711
+ npm test
712
+
713
+ # Run with coverage
714
+ npm run test:coverage
715
+
716
+ # Run in watch mode
717
+ npm run test:watch
718
+ ```
719
+
720
+ ### Building
721
+
722
+ ```bash
723
+ # Production build
724
+ npm run build
725
+
726
+ # Clean build artifacts
727
+ npm run clean
728
+
729
+ # Type check
730
+ npm run typecheck
731
+ ```
732
+
733
+ ## Related Packages
734
+
735
+ - [@easyling/sanity-connector](../plugin/README.md) - Studio plugin for manual translation
736
+ - [@easyling/sanity-connector-shared](../shared/README.md) - Shared translation logic
737
+
738
+ ## Support
739
+
740
+ For questions, issues, or feature requests:
741
+
742
+ - **Email**: support@easyling.com
743
+ - **Issues**: [GitHub Issues](https://github.com/easyling/el-sanity-connector/issues)
744
+ - **Documentation**: [Main README](../README.md) | [Plugin README](../plugin/README.md) | [Shared Library](../shared/README.md)
745
+
746
+ ## License
747
+
748
+ MIT