@procore/ai-translations 0.8.1 → 0.9.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 CHANGED
@@ -4,14 +4,14 @@ React package for AI-powered UI string translation in frontend apps. It provides
4
4
 
5
5
  - `AITranslationProvider` to initialize translation context
6
6
  - `AITranslateText` to render translated text
7
- - `useAITranslation` for direct access to translation and progress state
7
+ - `useAITranslation` for direct access to translation, consent, and progress state
8
8
 
9
9
  Need deeper implementation details? See the internal guide: [`README.internal.md`](./README.internal.md).
10
10
 
11
11
  This package supports two runtime strategies:
12
12
 
13
- - `frontend_translations`: browser-based translation (Chrome-family AI APIs)
14
- - `backend_translations`: server-side translation API
13
+ - `frontend_translations`: browser-based translation (Chrome-family AI APIs). Translation runs sequentially and is gated on user consent when an on-device model must be downloaded.
14
+ - `backend_translations`: server-side translation API. No on-device model downloads; model-consent popups are suppressed automatically.
15
15
 
16
16
  For Procore-specific operational details, see `README.internal.md`.
17
17
 
@@ -190,8 +190,21 @@ Returns:
190
190
  - `tool: string`
191
191
  - `translationProgress: { progress: number; current: number; total: number } | null`
192
192
  - `modelDownloadProgress: ModelDownloadProgress | null`
193
+ - `modelConsent: ModelConsentRequest | null` - non-null while the translation run is paused awaiting user consent to download a source→target model; `null` otherwise
194
+ - `confirmModelDownload: () => void` - call this (e.g. from a Download button's `onClick`) to provide the required user gesture, start the download, and resume translation; no-op when `modelConsent` is `null` or when the active strategy is `backend_translations`
193
195
  - `renderVersion: number | undefined`
194
196
 
197
+ ### `normalizeLanguage(language: string): string`
198
+
199
+ Utility that strips the region/script subtag from a BCP-47 language tag (e.g. `"en-US"` → `"en"`). Falls back to the original string when no subtag is present.
200
+
201
+ ```ts
202
+ import { normalizeLanguage } from '@procore/ai-translations';
203
+
204
+ normalizeLanguage('en-US'); // 'en'
205
+ normalizeLanguage('fr'); // 'fr'
206
+ ```
207
+
195
208
  ### Feature flag helpers
196
209
 
197
210
  - `AI_TRANSLATION_FEATURE_FLAG_KEY`
@@ -210,14 +223,35 @@ queueText --> manager[TranslationManager]
210
223
  manager --> strategy[TranslatorStrategy]
211
224
  strategy --> frontendClient[ChromeFrontendClient]
212
225
  strategy --> backendClient[BackendApiClient]
213
- frontendClient --> persist[PersistToIndexedDB]
226
+ frontendClient --> modelGate{ModelDownloadGate}
227
+ modelGate -- model ready --> translate[Translate]
228
+ modelGate -- model missing --> consentEvent[MODEL_CONSENT_REQUIRED event]
229
+ consentEvent --> popup[ModelDownloadConsentPopup]
230
+ popup -- user clicks Download --> confirmModelDownload[confirmModelDownload]
231
+ confirmModelDownload --> downloadModel[downloadModel]
232
+ downloadModel --> markReady[gate.markReady]
233
+ markReady --> translate
234
+ translate --> persist[PersistToIndexedDB]
214
235
  backendClient --> persist
215
236
  persist --> publish[PublishTranslationEvents]
216
237
  publish --> rerender[AITranslateTextRerender]
217
238
  rerender --> translatedUi[DisplayTranslatedText]
218
239
  ```
219
240
 
220
- At runtime, strings are registered through `ait()`, processed in batches, cached in memory and IndexedDB, and then rerendered via package events.
241
+ At runtime, strings are registered through `ait()`, processed sequentially (frontend strategy) or in configurable batches (backend strategy), cached in memory and IndexedDB, and then rerendered via package events.
242
+
243
+ ### Frontend model-download consent flow
244
+
245
+ When the `frontend_translations` strategy encounters a source→target model that has not yet been downloaded, Chrome requires a user gesture to initiate the download. The package handles this automatically:
246
+
247
+ 1. The translation run **pauses** and fires a `MODEL_CONSENT_REQUIRED` event carrying `{ sourceLanguage, targetLanguage }`.
248
+ 2. `useAITranslation().modelConsent` becomes non-null — render `<ModelDownloadConsentPopup />` (from `@procore/ai-translation-utils`) to surface the consent UI to the user.
249
+ 3. The user clicks the **Download** button, calling `confirmModelDownload()` which provides the required gesture, starts the download, and shows `<ModelDownloadProgressPopup />`.
250
+ 4. When the download completes, `modelDownloadGate.markReady(source, target)` is called and the paused translation run **resumes automatically**.
251
+ 5. Steps 1–4 repeat for each unique source→target model pair that is not yet cached. Already-downloaded models skip the consent step entirely.
252
+ 6. After all texts are translated the run terminates. Translated text is displayed immediately as each translation completes, not only at the end.
253
+
254
+ > **Important**: your app must render `<ModelDownloadConsentPopup />` inside `AITranslationProvider` for the consent flow to work. Without it, `ensureModelReady` will never resolve and the translation run will hang indefinitely.
221
255
 
222
256
  ## Configuration
223
257
 
@@ -64,6 +64,20 @@ interface ModelDownloadProgress {
64
64
  progress: number;
65
65
  /** True once the model has finished downloading */
66
66
  isComplete: boolean;
67
+ /** Source language of the model being downloaded (e.g. `"fr"`) */
68
+ sourceLanguage: string;
69
+ /** Target language of the model being downloaded (e.g. `"en"`) */
70
+ targetLanguage: string;
71
+ }
72
+ /**
73
+ * A pending request for the user to consent to downloading a specific
74
+ * source -> target language model before translation can continue.
75
+ */
76
+ interface ModelConsentRequest {
77
+ /** Source language of the model that needs downloading (e.g. `"fr"`). */
78
+ sourceLanguage: string;
79
+ /** Target language of the model that needs downloading (e.g. `"en"`). */
80
+ targetLanguage: string;
67
81
  }
68
82
  interface AITranslationContextValue {
69
83
  ait: (text: string) => Promise<string>;
@@ -77,6 +91,17 @@ interface AITranslationContextValue {
77
91
  translationProgress: TranslationProgress | null;
78
92
  /** Chrome AI model download progress; `null` until a download begins. */
79
93
  modelDownloadProgress: ModelDownloadProgress | null;
94
+ /**
95
+ * Pending model the translation run is paused on, awaiting the user's consent
96
+ * to download; `null` when no consent is required.
97
+ */
98
+ modelConsent: ModelConsentRequest | null;
99
+ /**
100
+ * Confirms the pending {@link modelConsent} download. Must be called from a
101
+ * user gesture (e.g. a click handler) since Chrome requires a gesture to
102
+ * start a model download.
103
+ */
104
+ confirmModelDownload: () => void;
80
105
  tool: string;
81
106
  /** Called by `useAIAnalytics` with a fully assembled event. The MFE is responsible for sending it. */
82
107
  onTrackAnalyticsEvent?: AIAnalyticsTracker;
@@ -171,7 +196,7 @@ type Action = (typeof ACTION)[keyof typeof ACTION];
171
196
  * Builds the `object` segment: `{pageContext}_{buttonType}_button`
172
197
  * e.g. `buildObject('view', 'translate')` → `'view_translate_button'`
173
198
  */
174
- declare function buildObject(pageContext: string, buttonType: ButtonType): string;
199
+ declare function buildObject(buttonType: ButtonType): string;
175
200
  /**
176
201
  * Builds a fully-qualified event key:
177
202
  * `ux.web.feature.{scope}.{tool}.{object}.{action}`
@@ -335,4 +360,4 @@ declare global {
335
360
  var _BACKEND_AI_TRANSLATION_IN_PROGRESS_: boolean;
336
361
  }
337
362
 
338
- export { ACTION, type AIAnalyticsEventProperties, type AIAnalyticsTracker, AITranslateText, type AITranslateTextProps, AITranslationProvider, AI_TRANSLATION_FEATURE_FLAG_KEY, type Action, type AnalyticEvent, BUTTON_TYPE, type BuildAnalyticEventParams, type ButtonType, CustomizableAITranslateText, type CustomizableAITranslateTextProps, type EventKeyParts, type Scope, type TextSegmenter, type TranslatableSegment, type TranslatedIconProps, type UseAIAnalyticsReturn, type UseConfigOptions, buildAnalyticEvent, buildEventKey, buildObject, getAITranslationLDId, isSupportedBrowser, useAIAnalytics, useAITranslation, useConfig };
363
+ export { ACTION, type AIAnalyticsEventProperties, type AIAnalyticsTracker, AITranslateText, type AITranslateTextProps, AITranslationProvider, AI_TRANSLATION_FEATURE_FLAG_KEY, type Action, type AnalyticEvent, BUTTON_TYPE, type BuildAnalyticEventParams, type ButtonType, CustomizableAITranslateText, type CustomizableAITranslateTextProps, type EventKeyParts, type ModelConsentRequest, type ModelDownloadProgress, type Scope, type TextSegmenter, type TranslatableSegment, type TranslatedIconProps, type UseAIAnalyticsReturn, type UseConfigOptions, buildAnalyticEvent, buildEventKey, buildObject, getAITranslationLDId, isSupportedBrowser, useAIAnalytics, useAITranslation, useConfig };
@@ -64,6 +64,20 @@ interface ModelDownloadProgress {
64
64
  progress: number;
65
65
  /** True once the model has finished downloading */
66
66
  isComplete: boolean;
67
+ /** Source language of the model being downloaded (e.g. `"fr"`) */
68
+ sourceLanguage: string;
69
+ /** Target language of the model being downloaded (e.g. `"en"`) */
70
+ targetLanguage: string;
71
+ }
72
+ /**
73
+ * A pending request for the user to consent to downloading a specific
74
+ * source -> target language model before translation can continue.
75
+ */
76
+ interface ModelConsentRequest {
77
+ /** Source language of the model that needs downloading (e.g. `"fr"`). */
78
+ sourceLanguage: string;
79
+ /** Target language of the model that needs downloading (e.g. `"en"`). */
80
+ targetLanguage: string;
67
81
  }
68
82
  interface AITranslationContextValue {
69
83
  ait: (text: string) => Promise<string>;
@@ -77,6 +91,17 @@ interface AITranslationContextValue {
77
91
  translationProgress: TranslationProgress | null;
78
92
  /** Chrome AI model download progress; `null` until a download begins. */
79
93
  modelDownloadProgress: ModelDownloadProgress | null;
94
+ /**
95
+ * Pending model the translation run is paused on, awaiting the user's consent
96
+ * to download; `null` when no consent is required.
97
+ */
98
+ modelConsent: ModelConsentRequest | null;
99
+ /**
100
+ * Confirms the pending {@link modelConsent} download. Must be called from a
101
+ * user gesture (e.g. a click handler) since Chrome requires a gesture to
102
+ * start a model download.
103
+ */
104
+ confirmModelDownload: () => void;
80
105
  tool: string;
81
106
  /** Called by `useAIAnalytics` with a fully assembled event. The MFE is responsible for sending it. */
82
107
  onTrackAnalyticsEvent?: AIAnalyticsTracker;
@@ -171,7 +196,7 @@ type Action = (typeof ACTION)[keyof typeof ACTION];
171
196
  * Builds the `object` segment: `{pageContext}_{buttonType}_button`
172
197
  * e.g. `buildObject('view', 'translate')` → `'view_translate_button'`
173
198
  */
174
- declare function buildObject(pageContext: string, buttonType: ButtonType): string;
199
+ declare function buildObject(buttonType: ButtonType): string;
175
200
  /**
176
201
  * Builds a fully-qualified event key:
177
202
  * `ux.web.feature.{scope}.{tool}.{object}.{action}`
@@ -335,4 +360,4 @@ declare global {
335
360
  var _BACKEND_AI_TRANSLATION_IN_PROGRESS_: boolean;
336
361
  }
337
362
 
338
- export { ACTION, type AIAnalyticsEventProperties, type AIAnalyticsTracker, AITranslateText, type AITranslateTextProps, AITranslationProvider, AI_TRANSLATION_FEATURE_FLAG_KEY, type Action, type AnalyticEvent, BUTTON_TYPE, type BuildAnalyticEventParams, type ButtonType, CustomizableAITranslateText, type CustomizableAITranslateTextProps, type EventKeyParts, type Scope, type TextSegmenter, type TranslatableSegment, type TranslatedIconProps, type UseAIAnalyticsReturn, type UseConfigOptions, buildAnalyticEvent, buildEventKey, buildObject, getAITranslationLDId, isSupportedBrowser, useAIAnalytics, useAITranslation, useConfig };
363
+ export { ACTION, type AIAnalyticsEventProperties, type AIAnalyticsTracker, AITranslateText, type AITranslateTextProps, AITranslationProvider, AI_TRANSLATION_FEATURE_FLAG_KEY, type Action, type AnalyticEvent, BUTTON_TYPE, type BuildAnalyticEventParams, type ButtonType, CustomizableAITranslateText, type CustomizableAITranslateTextProps, type EventKeyParts, type ModelConsentRequest, type ModelDownloadProgress, type Scope, type TextSegmenter, type TranslatableSegment, type TranslatedIconProps, type UseAIAnalyticsReturn, type UseConfigOptions, buildAnalyticEvent, buildEventKey, buildObject, getAITranslationLDId, isSupportedBrowser, useAIAnalytics, useAITranslation, useConfig };