@procore/ai-translations 0.7.0 → 0.8.1

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
@@ -81,6 +81,104 @@ Props:
81
81
  - `showHighlight?: boolean` (default `false`)
82
82
  - `translatedIconProps?: TranslatedIconProps`
83
83
 
84
+ ### `CustomizableAITranslateText`
85
+
86
+ Translates only chosen substrings of a string. Use anywhere a value mixes translatable and non-translatable text — descriptions, status fields, breadcrumbs, tags, cards, forms, modals — not just data tables.
87
+
88
+ Supply a `segmenter` function that splits the full text into an ordered list of `{ text, translate }` segments. Only segments with `translate: true` are sent through the AI pipeline; the rest are rendered verbatim.
89
+
90
+ Props:
91
+
92
+ - `text: string` — the full text value, passed to `segmenter`
93
+ - `shouldTranslate: boolean` — master switch; when `false` all segments render as plain text
94
+ - `showHighlight?: boolean` (default `false`) — whether to show the `TranslatedIcon`
95
+ - `highlightMode?: 'segment' | 'cell'` (default `'segment'`) — see below
96
+ - `segmenter?: (text: string) => TranslatableSegment[]` — split rule; when omitted the whole text is one translatable segment
97
+ - `translatedIconProps?: TranslatedIconProps`
98
+
99
+ #### Segmenter examples
100
+
101
+ **`"{code} - {label}"`** — keep the code prefix, translate only the label:
102
+
103
+ ```tsx
104
+ import { CustomizableAITranslateText } from '@procore/ai-translations';
105
+
106
+ <CustomizableAITranslateText
107
+ text="INS-001 - Safety Inspection"
108
+ shouldTranslate={true}
109
+ segmenter={(text) => {
110
+ const idx = text.indexOf(' - ');
111
+ if (idx === -1) return [{ text, translate: true }];
112
+ return [
113
+ { text: text.slice(0, idx + 3), translate: false }, // "INS-001 - "
114
+ { text: text.slice(idx + 3), translate: true }, // "Safety Inspection"
115
+ ];
116
+ }}
117
+ />;
118
+ ```
119
+
120
+ **`"Key: Value"`** — keep the field name, translate only the value:
121
+
122
+ ```tsx
123
+ <CustomizableAITranslateText
124
+ text="Status: Awaiting Review"
125
+ shouldTranslate={true}
126
+ segmenter={(text) => {
127
+ const idx = text.indexOf(': ');
128
+ if (idx === -1) return [{ text, translate: true }];
129
+ return [
130
+ { text: text.slice(0, idx + 2), translate: false }, // "Status: "
131
+ { text: text.slice(idx + 2), translate: true }, // "Awaiting Review"
132
+ ];
133
+ }}
134
+ />
135
+ ```
136
+
137
+ **`"A | B"`** — translate both halves, keep the divider:
138
+
139
+ ```tsx
140
+ <CustomizableAITranslateText
141
+ text="Safety | Critical"
142
+ shouldTranslate={true}
143
+ segmenter={(text) => {
144
+ const idx = text.indexOf(' | ');
145
+ if (idx === -1) return [{ text, translate: true }];
146
+ return [
147
+ { text: text.slice(0, idx), translate: true }, // "Safety"
148
+ { text: ' | ', translate: false }, // " | "
149
+ { text: text.slice(idx + 3), translate: true }, // "Critical"
150
+ ];
151
+ }}
152
+ />
153
+ ```
154
+
155
+ **`"A > B > C"` breadcrumb** — translate every crumb independently:
156
+
157
+ ```tsx
158
+ <CustomizableAITranslateText
159
+ text="Projects > Building A > Floor 3"
160
+ shouldTranslate={true}
161
+ segmenter={(text) => {
162
+ const parts = text.split(' > ');
163
+ return parts.flatMap((part, i) => [
164
+ { text: part, translate: true },
165
+ ...(i < parts.length - 1 ? [{ text: ' > ', translate: false }] : []),
166
+ ]);
167
+ }}
168
+ />
169
+ ```
170
+
171
+ #### `highlightMode`
172
+
173
+ Controls where `TranslatedIcon` appears when a translation actually changes the text:
174
+
175
+ | Mode | Behavior |
176
+ | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
177
+ | `'segment'` (default) | One icon next to each segment whose translated output differs from its source. Precisely marks which substrings are AI-generated. |
178
+ | `'cell'` | A single icon at the very start of the value, only when at least one segment's output actually changed. Cleaner for values with many translated segments. |
179
+
180
+ `showHighlight={false}` suppresses all icons regardless of mode.
181
+
84
182
  ### `useAITranslation()`
85
183
 
86
184
  Returns:
@@ -263,10 +263,64 @@ interface AITranslateTextProps {
263
263
  */
264
264
  declare const AITranslateText: React.FC<AITranslateTextProps>;
265
265
 
266
+ /**
267
+ * A single piece of a text value. When `translate` is `true`, the segment is
268
+ * sent through the AI translation pipeline; otherwise it is rendered as plain
269
+ * text and never reaches the registry.
270
+ */
271
+ interface TranslatableSegment {
272
+ text: string;
273
+ translate: boolean;
274
+ }
275
+ /**
276
+ * Pure function that turns a string into an ordered list of translatable /
277
+ * non-translatable segments. Called on every render, so it should be
278
+ * inexpensive and free of side effects.
279
+ */
280
+ type TextSegmenter = (text: string) => TranslatableSegment[];
281
+ interface CustomizableAITranslateTextProps {
282
+ /** The full text value. Fed to the segmenter or — when no segmenter is
283
+ * supplied — translated as a single piece. */
284
+ text: string;
285
+ shouldTranslate: boolean;
286
+ /** Master switch for the highlight icon. Defaults to `false`. When `true`,
287
+ * a single icon is shown at the start of the value if at least one segment's
288
+ * translation actually changed the source text. */
289
+ showHighlight?: boolean;
290
+ segmenter?: TextSegmenter;
291
+ translatedIconProps?: TranslatedIconProps;
292
+ }
293
+ /**
294
+ * Generic component for translating chosen substrings of a string. Owns its
295
+ * translation lifecycle directly (does not compose `AITranslateText`), so it
296
+ * can decide whether a cell-level highlight icon is appropriate based on
297
+ * whether any segment's translation actually changed the source.
298
+ *
299
+ * When `showHighlight` is `true`, a single icon is rendered at the start of
300
+ * the value if at least one segment changed — i.e. always cell-level mode.
301
+ *
302
+ * @example
303
+ * // Translate only the label half of "{code} - {label}"
304
+ * <CustomizableAITranslateText
305
+ * text="INS-001 - Safety Inspection"
306
+ * shouldTranslate={true}
307
+ * showHighlight={true}
308
+ * segmenter={(t) => {
309
+ * const idx = t.indexOf(' - ');
310
+ * if (idx === -1) return [{ text: t, translate: true }];
311
+ * return [
312
+ * { text: t.slice(0, idx + 3), translate: false },
313
+ * { text: t.slice(idx + 3), translate: true },
314
+ * ];
315
+ * }}
316
+ * />
317
+ */
318
+ declare const CustomizableAITranslateText: React.FC<CustomizableAITranslateTextProps>;
319
+
266
320
  /**
267
321
  * The key used to store/retrieve the feature flag in local storage.
268
322
  */
269
- declare const AI_TRANSLATION_FEATURE_FLAG_KEY = "pe_ai_translations_web_program_visible";
323
+ declare const AI_TRANSLATION_FEATURE_FLAG_KEY = "ai-translation";
270
324
  /**
271
325
  * Retrieves the LD ID for the AI translation feature flag based on the domain.
272
326
  * @param domain - The domain to determine the LD ID for
@@ -281,4 +335,4 @@ declare global {
281
335
  var _BACKEND_AI_TRANSLATION_IN_PROGRESS_: boolean;
282
336
  }
283
337
 
284
- 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, type EventKeyParts, type Scope, type TranslatedIconProps, type UseAIAnalyticsReturn, type UseConfigOptions, buildAnalyticEvent, buildEventKey, buildObject, getAITranslationLDId, isSupportedBrowser, useAIAnalytics, useAITranslation, useConfig };
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 };
@@ -263,10 +263,64 @@ interface AITranslateTextProps {
263
263
  */
264
264
  declare const AITranslateText: React.FC<AITranslateTextProps>;
265
265
 
266
+ /**
267
+ * A single piece of a text value. When `translate` is `true`, the segment is
268
+ * sent through the AI translation pipeline; otherwise it is rendered as plain
269
+ * text and never reaches the registry.
270
+ */
271
+ interface TranslatableSegment {
272
+ text: string;
273
+ translate: boolean;
274
+ }
275
+ /**
276
+ * Pure function that turns a string into an ordered list of translatable /
277
+ * non-translatable segments. Called on every render, so it should be
278
+ * inexpensive and free of side effects.
279
+ */
280
+ type TextSegmenter = (text: string) => TranslatableSegment[];
281
+ interface CustomizableAITranslateTextProps {
282
+ /** The full text value. Fed to the segmenter or — when no segmenter is
283
+ * supplied — translated as a single piece. */
284
+ text: string;
285
+ shouldTranslate: boolean;
286
+ /** Master switch for the highlight icon. Defaults to `false`. When `true`,
287
+ * a single icon is shown at the start of the value if at least one segment's
288
+ * translation actually changed the source text. */
289
+ showHighlight?: boolean;
290
+ segmenter?: TextSegmenter;
291
+ translatedIconProps?: TranslatedIconProps;
292
+ }
293
+ /**
294
+ * Generic component for translating chosen substrings of a string. Owns its
295
+ * translation lifecycle directly (does not compose `AITranslateText`), so it
296
+ * can decide whether a cell-level highlight icon is appropriate based on
297
+ * whether any segment's translation actually changed the source.
298
+ *
299
+ * When `showHighlight` is `true`, a single icon is rendered at the start of
300
+ * the value if at least one segment changed — i.e. always cell-level mode.
301
+ *
302
+ * @example
303
+ * // Translate only the label half of "{code} - {label}"
304
+ * <CustomizableAITranslateText
305
+ * text="INS-001 - Safety Inspection"
306
+ * shouldTranslate={true}
307
+ * showHighlight={true}
308
+ * segmenter={(t) => {
309
+ * const idx = t.indexOf(' - ');
310
+ * if (idx === -1) return [{ text: t, translate: true }];
311
+ * return [
312
+ * { text: t.slice(0, idx + 3), translate: false },
313
+ * { text: t.slice(idx + 3), translate: true },
314
+ * ];
315
+ * }}
316
+ * />
317
+ */
318
+ declare const CustomizableAITranslateText: React.FC<CustomizableAITranslateTextProps>;
319
+
266
320
  /**
267
321
  * The key used to store/retrieve the feature flag in local storage.
268
322
  */
269
- declare const AI_TRANSLATION_FEATURE_FLAG_KEY = "pe_ai_translations_web_program_visible";
323
+ declare const AI_TRANSLATION_FEATURE_FLAG_KEY = "ai-translation";
270
324
  /**
271
325
  * Retrieves the LD ID for the AI translation feature flag based on the domain.
272
326
  * @param domain - The domain to determine the LD ID for
@@ -281,4 +335,4 @@ declare global {
281
335
  var _BACKEND_AI_TRANSLATION_IN_PROGRESS_: boolean;
282
336
  }
283
337
 
284
- 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, type EventKeyParts, type Scope, type TranslatedIconProps, type UseAIAnalyticsReturn, type UseConfigOptions, buildAnalyticEvent, buildEventKey, buildObject, getAITranslationLDId, isSupportedBrowser, useAIAnalytics, useAITranslation, useConfig };
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 };
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
9
  var __export = (target, all) => {
@@ -16,6 +18,14 @@ var __copyProps = (to, from, except, desc) => {
16
18
  }
17
19
  return to;
18
20
  };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
19
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
30
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
21
31
 
@@ -27,6 +37,7 @@ __export(index_exports, {
27
37
  AITranslationProvider: () => AITranslationProvider,
28
38
  AI_TRANSLATION_FEATURE_FLAG_KEY: () => AI_TRANSLATION_FEATURE_FLAG_KEY,
29
39
  BUTTON_TYPE: () => BUTTON_TYPE,
40
+ CustomizableAITranslateText: () => CustomizableAITranslateText,
30
41
  buildAnalyticEvent: () => buildAnalyticEvent,
31
42
  buildEventKey: () => buildEventKey,
32
43
  buildObject: () => buildObject,
@@ -1254,12 +1265,13 @@ var _ChromeTranslator = class _ChromeTranslator {
1254
1265
  };
1255
1266
  } catch (error) {
1256
1267
  const message = error instanceof Error ? error.message : "Unknown error";
1268
+ const isPermanentFailure = error instanceof DOMException && error.name === "NotSupportedError";
1257
1269
  return {
1258
1270
  translation: text,
1259
1271
  sourceLanguage: targetLanguage,
1260
1272
  success: false,
1261
1273
  errorMessage: message,
1262
- retryable: true
1274
+ retryable: !isPermanentFailure
1263
1275
  };
1264
1276
  }
1265
1277
  }
@@ -1825,9 +1837,93 @@ var AITranslateText = ({
1825
1837
  ] });
1826
1838
  };
1827
1839
 
1840
+ // src/components/CustomizableAITranslateText.tsx
1841
+ var import_react6 = __toESM(require("react"));
1842
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1843
+ var CustomizableAITranslateText = ({
1844
+ text,
1845
+ shouldTranslate,
1846
+ showHighlight = false,
1847
+ segmenter,
1848
+ translatedIconProps
1849
+ }) => {
1850
+ const context = (0, import_react6.useContext)(AITranslationContext);
1851
+ const ait = context == null ? void 0 : context.ait;
1852
+ const segments = (0, import_react6.useMemo)(
1853
+ () => segmenter ? segmenter(text) : [{ text, translate: shouldTranslate }],
1854
+ [segmenter, text, shouldTranslate]
1855
+ );
1856
+ const [displayTexts, setDisplayTexts] = (0, import_react6.useState)(
1857
+ () => segments.map((s) => s.text)
1858
+ );
1859
+ const segmentsKey = JSON.stringify(segments);
1860
+ (0, import_react6.useEffect)(() => {
1861
+ setDisplayTexts(segments.map((s) => s.text));
1862
+ }, [segmentsKey]);
1863
+ (0, import_react6.useEffect)(() => {
1864
+ if (!ait || !shouldTranslate) {
1865
+ setDisplayTexts(segments.map((s) => s.text));
1866
+ return;
1867
+ }
1868
+ let cancelled = false;
1869
+ Promise.all(
1870
+ segments.map((s) => s.translate ? ait(s.text) : Promise.resolve(s.text))
1871
+ ).then((resolved) => {
1872
+ if (cancelled) return;
1873
+ setDisplayTexts(resolved);
1874
+ }).catch(() => {
1875
+ if (cancelled) return;
1876
+ setDisplayTexts(segments.map((s) => s.text));
1877
+ });
1878
+ return () => {
1879
+ cancelled = true;
1880
+ };
1881
+ }, [ait, shouldTranslate, segmentsKey]);
1882
+ const eventHandlerRef = (0, import_react6.useRef)(null);
1883
+ (0, import_react6.useEffect)(() => {
1884
+ if (!context) return;
1885
+ if (!eventHandlerRef.current) {
1886
+ eventHandlerRef.current = new EventHandler(context.tool);
1887
+ }
1888
+ const unsubscribe = eventHandlerRef.current.subscribeToRerenderEvent(
1889
+ async (sourceTexts) => {
1890
+ if (!shouldTranslate) return;
1891
+ if (!sourceTexts || sourceTexts.length === 0) return;
1892
+ const sourceSet = new Set(sourceTexts);
1893
+ const targets = segments.map((s, i) => ({ s, i })).filter(({ s }) => s.translate && sourceSet.has(s.text));
1894
+ if (targets.length === 0) return;
1895
+ const fresh = await Promise.all(
1896
+ targets.map(({ s }) => context.ait(s.text))
1897
+ );
1898
+ setDisplayTexts((prev) => {
1899
+ const next = [...prev];
1900
+ targets.forEach(({ i }, j) => {
1901
+ const value = fresh[j];
1902
+ if (value !== void 0) next[i] = value;
1903
+ });
1904
+ return next;
1905
+ });
1906
+ }
1907
+ );
1908
+ return () => unsubscribe();
1909
+ }, [context, shouldTranslate, segmentsKey]);
1910
+ const changedFlags = segments.map(
1911
+ (s, i) => s.translate && shouldTranslate && (displayTexts[i] ?? s.text) !== s.text
1912
+ );
1913
+ const anyChanged = changedFlags.some(Boolean);
1914
+ const showCellIcon = showHighlight && anyChanged;
1915
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1916
+ showCellIcon && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TranslatedIcon, { ...translatedIconProps }),
1917
+ segments.map((seg, i) => {
1918
+ const display = displayTexts[i] ?? seg.text;
1919
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react6.default.Fragment, { children: display }, `${i}-${seg.text}`);
1920
+ })
1921
+ ] });
1922
+ };
1923
+
1828
1924
  // src/utils/featureFlag.ts
1829
1925
  var import_web_sdk_mfe_utils = require("@procore/web-sdk-mfe-utils");
1830
- var AI_TRANSLATION_FEATURE_FLAG_KEY = "pe_ai_translations_web_program_visible";
1926
+ var AI_TRANSLATION_FEATURE_FLAG_KEY = "ai-translation";
1831
1927
  var getAITranslationLDId = (domain) => {
1832
1928
  const { environment, zone } = (0, import_web_sdk_mfe_utils.getProcoreZone)(domain);
1833
1929
  if ((0, import_web_sdk_mfe_utils.isFederalZone)(zone)) {
@@ -1850,6 +1946,7 @@ var getAITranslationLDId = (domain) => {
1850
1946
  AITranslationProvider,
1851
1947
  AI_TRANSLATION_FEATURE_FLAG_KEY,
1852
1948
  BUTTON_TYPE,
1949
+ CustomizableAITranslateText,
1853
1950
  buildAnalyticEvent,
1854
1951
  buildEventKey,
1855
1952
  buildObject,
@@ -1224,12 +1224,13 @@ var _ChromeTranslator = class _ChromeTranslator {
1224
1224
  };
1225
1225
  } catch (error) {
1226
1226
  const message = error instanceof Error ? error.message : "Unknown error";
1227
+ const isPermanentFailure = error instanceof DOMException && error.name === "NotSupportedError";
1227
1228
  return {
1228
1229
  translation: text,
1229
1230
  sourceLanguage: targetLanguage,
1230
1231
  success: false,
1231
1232
  errorMessage: message,
1232
- retryable: true
1233
+ retryable: !isPermanentFailure
1233
1234
  };
1234
1235
  }
1235
1236
  }
@@ -1801,9 +1802,93 @@ var AITranslateText = ({
1801
1802
  ] });
1802
1803
  };
1803
1804
 
1805
+ // src/components/CustomizableAITranslateText.tsx
1806
+ import React4, { useContext as useContext4, useEffect as useEffect3, useMemo, useRef as useRef3, useState as useState3 } from "react";
1807
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
1808
+ var CustomizableAITranslateText = ({
1809
+ text,
1810
+ shouldTranslate,
1811
+ showHighlight = false,
1812
+ segmenter,
1813
+ translatedIconProps
1814
+ }) => {
1815
+ const context = useContext4(AITranslationContext);
1816
+ const ait = context == null ? void 0 : context.ait;
1817
+ const segments = useMemo(
1818
+ () => segmenter ? segmenter(text) : [{ text, translate: shouldTranslate }],
1819
+ [segmenter, text, shouldTranslate]
1820
+ );
1821
+ const [displayTexts, setDisplayTexts] = useState3(
1822
+ () => segments.map((s) => s.text)
1823
+ );
1824
+ const segmentsKey = JSON.stringify(segments);
1825
+ useEffect3(() => {
1826
+ setDisplayTexts(segments.map((s) => s.text));
1827
+ }, [segmentsKey]);
1828
+ useEffect3(() => {
1829
+ if (!ait || !shouldTranslate) {
1830
+ setDisplayTexts(segments.map((s) => s.text));
1831
+ return;
1832
+ }
1833
+ let cancelled = false;
1834
+ Promise.all(
1835
+ segments.map((s) => s.translate ? ait(s.text) : Promise.resolve(s.text))
1836
+ ).then((resolved) => {
1837
+ if (cancelled) return;
1838
+ setDisplayTexts(resolved);
1839
+ }).catch(() => {
1840
+ if (cancelled) return;
1841
+ setDisplayTexts(segments.map((s) => s.text));
1842
+ });
1843
+ return () => {
1844
+ cancelled = true;
1845
+ };
1846
+ }, [ait, shouldTranslate, segmentsKey]);
1847
+ const eventHandlerRef = useRef3(null);
1848
+ useEffect3(() => {
1849
+ if (!context) return;
1850
+ if (!eventHandlerRef.current) {
1851
+ eventHandlerRef.current = new EventHandler(context.tool);
1852
+ }
1853
+ const unsubscribe = eventHandlerRef.current.subscribeToRerenderEvent(
1854
+ async (sourceTexts) => {
1855
+ if (!shouldTranslate) return;
1856
+ if (!sourceTexts || sourceTexts.length === 0) return;
1857
+ const sourceSet = new Set(sourceTexts);
1858
+ const targets = segments.map((s, i) => ({ s, i })).filter(({ s }) => s.translate && sourceSet.has(s.text));
1859
+ if (targets.length === 0) return;
1860
+ const fresh = await Promise.all(
1861
+ targets.map(({ s }) => context.ait(s.text))
1862
+ );
1863
+ setDisplayTexts((prev) => {
1864
+ const next = [...prev];
1865
+ targets.forEach(({ i }, j) => {
1866
+ const value = fresh[j];
1867
+ if (value !== void 0) next[i] = value;
1868
+ });
1869
+ return next;
1870
+ });
1871
+ }
1872
+ );
1873
+ return () => unsubscribe();
1874
+ }, [context, shouldTranslate, segmentsKey]);
1875
+ const changedFlags = segments.map(
1876
+ (s, i) => s.translate && shouldTranslate && (displayTexts[i] ?? s.text) !== s.text
1877
+ );
1878
+ const anyChanged = changedFlags.some(Boolean);
1879
+ const showCellIcon = showHighlight && anyChanged;
1880
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
1881
+ showCellIcon && /* @__PURE__ */ jsx4(TranslatedIcon, { ...translatedIconProps }),
1882
+ segments.map((seg, i) => {
1883
+ const display = displayTexts[i] ?? seg.text;
1884
+ return /* @__PURE__ */ jsx4(React4.Fragment, { children: display }, `${i}-${seg.text}`);
1885
+ })
1886
+ ] });
1887
+ };
1888
+
1804
1889
  // src/utils/featureFlag.ts
1805
1890
  import { isFederalZone, getProcoreZone } from "@procore/web-sdk-mfe-utils";
1806
- var AI_TRANSLATION_FEATURE_FLAG_KEY = "pe_ai_translations_web_program_visible";
1891
+ var AI_TRANSLATION_FEATURE_FLAG_KEY = "ai-translation";
1807
1892
  var getAITranslationLDId = (domain) => {
1808
1893
  const { environment, zone } = getProcoreZone(domain);
1809
1894
  if (isFederalZone(zone)) {
@@ -1825,6 +1910,7 @@ export {
1825
1910
  AITranslationProvider,
1826
1911
  AI_TRANSLATION_FEATURE_FLAG_KEY,
1827
1912
  BUTTON_TYPE,
1913
+ CustomizableAITranslateText,
1828
1914
  buildAnalyticEvent,
1829
1915
  buildEventKey,
1830
1916
  buildObject,
@@ -263,10 +263,64 @@ interface AITranslateTextProps {
263
263
  */
264
264
  declare const AITranslateText: React.FC<AITranslateTextProps>;
265
265
 
266
+ /**
267
+ * A single piece of a text value. When `translate` is `true`, the segment is
268
+ * sent through the AI translation pipeline; otherwise it is rendered as plain
269
+ * text and never reaches the registry.
270
+ */
271
+ interface TranslatableSegment {
272
+ text: string;
273
+ translate: boolean;
274
+ }
275
+ /**
276
+ * Pure function that turns a string into an ordered list of translatable /
277
+ * non-translatable segments. Called on every render, so it should be
278
+ * inexpensive and free of side effects.
279
+ */
280
+ type TextSegmenter = (text: string) => TranslatableSegment[];
281
+ interface CustomizableAITranslateTextProps {
282
+ /** The full text value. Fed to the segmenter or — when no segmenter is
283
+ * supplied — translated as a single piece. */
284
+ text: string;
285
+ shouldTranslate: boolean;
286
+ /** Master switch for the highlight icon. Defaults to `false`. When `true`,
287
+ * a single icon is shown at the start of the value if at least one segment's
288
+ * translation actually changed the source text. */
289
+ showHighlight?: boolean;
290
+ segmenter?: TextSegmenter;
291
+ translatedIconProps?: TranslatedIconProps;
292
+ }
293
+ /**
294
+ * Generic component for translating chosen substrings of a string. Owns its
295
+ * translation lifecycle directly (does not compose `AITranslateText`), so it
296
+ * can decide whether a cell-level highlight icon is appropriate based on
297
+ * whether any segment's translation actually changed the source.
298
+ *
299
+ * When `showHighlight` is `true`, a single icon is rendered at the start of
300
+ * the value if at least one segment changed — i.e. always cell-level mode.
301
+ *
302
+ * @example
303
+ * // Translate only the label half of "{code} - {label}"
304
+ * <CustomizableAITranslateText
305
+ * text="INS-001 - Safety Inspection"
306
+ * shouldTranslate={true}
307
+ * showHighlight={true}
308
+ * segmenter={(t) => {
309
+ * const idx = t.indexOf(' - ');
310
+ * if (idx === -1) return [{ text: t, translate: true }];
311
+ * return [
312
+ * { text: t.slice(0, idx + 3), translate: false },
313
+ * { text: t.slice(idx + 3), translate: true },
314
+ * ];
315
+ * }}
316
+ * />
317
+ */
318
+ declare const CustomizableAITranslateText: React.FC<CustomizableAITranslateTextProps>;
319
+
266
320
  /**
267
321
  * The key used to store/retrieve the feature flag in local storage.
268
322
  */
269
- declare const AI_TRANSLATION_FEATURE_FLAG_KEY = "pe_ai_translations_web_program_visible";
323
+ declare const AI_TRANSLATION_FEATURE_FLAG_KEY = "ai-translation";
270
324
  /**
271
325
  * Retrieves the LD ID for the AI translation feature flag based on the domain.
272
326
  * @param domain - The domain to determine the LD ID for
@@ -281,4 +335,4 @@ declare global {
281
335
  var _BACKEND_AI_TRANSLATION_IN_PROGRESS_: boolean;
282
336
  }
283
337
 
284
- 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, type EventKeyParts, type Scope, type TranslatedIconProps, type UseAIAnalyticsReturn, type UseConfigOptions, buildAnalyticEvent, buildEventKey, buildObject, getAITranslationLDId, isSupportedBrowser, useAIAnalytics, useAITranslation, useConfig };
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 };
@@ -263,10 +263,64 @@ interface AITranslateTextProps {
263
263
  */
264
264
  declare const AITranslateText: React.FC<AITranslateTextProps>;
265
265
 
266
+ /**
267
+ * A single piece of a text value. When `translate` is `true`, the segment is
268
+ * sent through the AI translation pipeline; otherwise it is rendered as plain
269
+ * text and never reaches the registry.
270
+ */
271
+ interface TranslatableSegment {
272
+ text: string;
273
+ translate: boolean;
274
+ }
275
+ /**
276
+ * Pure function that turns a string into an ordered list of translatable /
277
+ * non-translatable segments. Called on every render, so it should be
278
+ * inexpensive and free of side effects.
279
+ */
280
+ type TextSegmenter = (text: string) => TranslatableSegment[];
281
+ interface CustomizableAITranslateTextProps {
282
+ /** The full text value. Fed to the segmenter or — when no segmenter is
283
+ * supplied — translated as a single piece. */
284
+ text: string;
285
+ shouldTranslate: boolean;
286
+ /** Master switch for the highlight icon. Defaults to `false`. When `true`,
287
+ * a single icon is shown at the start of the value if at least one segment's
288
+ * translation actually changed the source text. */
289
+ showHighlight?: boolean;
290
+ segmenter?: TextSegmenter;
291
+ translatedIconProps?: TranslatedIconProps;
292
+ }
293
+ /**
294
+ * Generic component for translating chosen substrings of a string. Owns its
295
+ * translation lifecycle directly (does not compose `AITranslateText`), so it
296
+ * can decide whether a cell-level highlight icon is appropriate based on
297
+ * whether any segment's translation actually changed the source.
298
+ *
299
+ * When `showHighlight` is `true`, a single icon is rendered at the start of
300
+ * the value if at least one segment changed — i.e. always cell-level mode.
301
+ *
302
+ * @example
303
+ * // Translate only the label half of "{code} - {label}"
304
+ * <CustomizableAITranslateText
305
+ * text="INS-001 - Safety Inspection"
306
+ * shouldTranslate={true}
307
+ * showHighlight={true}
308
+ * segmenter={(t) => {
309
+ * const idx = t.indexOf(' - ');
310
+ * if (idx === -1) return [{ text: t, translate: true }];
311
+ * return [
312
+ * { text: t.slice(0, idx + 3), translate: false },
313
+ * { text: t.slice(idx + 3), translate: true },
314
+ * ];
315
+ * }}
316
+ * />
317
+ */
318
+ declare const CustomizableAITranslateText: React.FC<CustomizableAITranslateTextProps>;
319
+
266
320
  /**
267
321
  * The key used to store/retrieve the feature flag in local storage.
268
322
  */
269
- declare const AI_TRANSLATION_FEATURE_FLAG_KEY = "pe_ai_translations_web_program_visible";
323
+ declare const AI_TRANSLATION_FEATURE_FLAG_KEY = "ai-translation";
270
324
  /**
271
325
  * Retrieves the LD ID for the AI translation feature flag based on the domain.
272
326
  * @param domain - The domain to determine the LD ID for
@@ -281,4 +335,4 @@ declare global {
281
335
  var _BACKEND_AI_TRANSLATION_IN_PROGRESS_: boolean;
282
336
  }
283
337
 
284
- 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, type EventKeyParts, type Scope, type TranslatedIconProps, type UseAIAnalyticsReturn, type UseConfigOptions, buildAnalyticEvent, buildEventKey, buildObject, getAITranslationLDId, isSupportedBrowser, useAIAnalytics, useAITranslation, useConfig };
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 };
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -25,6 +35,7 @@ __export(index_exports, {
25
35
  AITranslationProvider: () => AITranslationProvider,
26
36
  AI_TRANSLATION_FEATURE_FLAG_KEY: () => AI_TRANSLATION_FEATURE_FLAG_KEY,
27
37
  BUTTON_TYPE: () => BUTTON_TYPE,
38
+ CustomizableAITranslateText: () => CustomizableAITranslateText,
28
39
  buildAnalyticEvent: () => buildAnalyticEvent,
29
40
  buildEventKey: () => buildEventKey,
30
41
  buildObject: () => buildObject,
@@ -1233,12 +1244,13 @@ var ChromeTranslator = class _ChromeTranslator {
1233
1244
  };
1234
1245
  } catch (error) {
1235
1246
  const message = error instanceof Error ? error.message : "Unknown error";
1247
+ const isPermanentFailure = error instanceof DOMException && error.name === "NotSupportedError";
1236
1248
  return {
1237
1249
  translation: text,
1238
1250
  sourceLanguage: targetLanguage,
1239
1251
  success: false,
1240
1252
  errorMessage: message,
1241
- retryable: true
1253
+ retryable: !isPermanentFailure
1242
1254
  };
1243
1255
  }
1244
1256
  }
@@ -1802,9 +1814,93 @@ var AITranslateText = ({
1802
1814
  ] });
1803
1815
  };
1804
1816
 
1817
+ // src/components/CustomizableAITranslateText.tsx
1818
+ var import_react6 = __toESM(require("react"));
1819
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1820
+ var CustomizableAITranslateText = ({
1821
+ text,
1822
+ shouldTranslate,
1823
+ showHighlight = false,
1824
+ segmenter,
1825
+ translatedIconProps
1826
+ }) => {
1827
+ const context = (0, import_react6.useContext)(AITranslationContext);
1828
+ const ait = context?.ait;
1829
+ const segments = (0, import_react6.useMemo)(
1830
+ () => segmenter ? segmenter(text) : [{ text, translate: shouldTranslate }],
1831
+ [segmenter, text, shouldTranslate]
1832
+ );
1833
+ const [displayTexts, setDisplayTexts] = (0, import_react6.useState)(
1834
+ () => segments.map((s) => s.text)
1835
+ );
1836
+ const segmentsKey = JSON.stringify(segments);
1837
+ (0, import_react6.useEffect)(() => {
1838
+ setDisplayTexts(segments.map((s) => s.text));
1839
+ }, [segmentsKey]);
1840
+ (0, import_react6.useEffect)(() => {
1841
+ if (!ait || !shouldTranslate) {
1842
+ setDisplayTexts(segments.map((s) => s.text));
1843
+ return;
1844
+ }
1845
+ let cancelled = false;
1846
+ Promise.all(
1847
+ segments.map((s) => s.translate ? ait(s.text) : Promise.resolve(s.text))
1848
+ ).then((resolved) => {
1849
+ if (cancelled) return;
1850
+ setDisplayTexts(resolved);
1851
+ }).catch(() => {
1852
+ if (cancelled) return;
1853
+ setDisplayTexts(segments.map((s) => s.text));
1854
+ });
1855
+ return () => {
1856
+ cancelled = true;
1857
+ };
1858
+ }, [ait, shouldTranslate, segmentsKey]);
1859
+ const eventHandlerRef = (0, import_react6.useRef)(null);
1860
+ (0, import_react6.useEffect)(() => {
1861
+ if (!context) return;
1862
+ if (!eventHandlerRef.current) {
1863
+ eventHandlerRef.current = new EventHandler(context.tool);
1864
+ }
1865
+ const unsubscribe = eventHandlerRef.current.subscribeToRerenderEvent(
1866
+ async (sourceTexts) => {
1867
+ if (!shouldTranslate) return;
1868
+ if (!sourceTexts || sourceTexts.length === 0) return;
1869
+ const sourceSet = new Set(sourceTexts);
1870
+ const targets = segments.map((s, i) => ({ s, i })).filter(({ s }) => s.translate && sourceSet.has(s.text));
1871
+ if (targets.length === 0) return;
1872
+ const fresh = await Promise.all(
1873
+ targets.map(({ s }) => context.ait(s.text))
1874
+ );
1875
+ setDisplayTexts((prev) => {
1876
+ const next = [...prev];
1877
+ targets.forEach(({ i }, j) => {
1878
+ const value = fresh[j];
1879
+ if (value !== void 0) next[i] = value;
1880
+ });
1881
+ return next;
1882
+ });
1883
+ }
1884
+ );
1885
+ return () => unsubscribe();
1886
+ }, [context, shouldTranslate, segmentsKey]);
1887
+ const changedFlags = segments.map(
1888
+ (s, i) => s.translate && shouldTranslate && (displayTexts[i] ?? s.text) !== s.text
1889
+ );
1890
+ const anyChanged = changedFlags.some(Boolean);
1891
+ const showCellIcon = showHighlight && anyChanged;
1892
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1893
+ showCellIcon && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TranslatedIcon, { ...translatedIconProps }),
1894
+ segments.map((seg, i) => {
1895
+ const display = displayTexts[i] ?? seg.text;
1896
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react6.default.Fragment, { children: display }, `${i}-${seg.text}`);
1897
+ })
1898
+ ] });
1899
+ };
1900
+
1805
1901
  // src/utils/featureFlag.ts
1806
1902
  var import_web_sdk_mfe_utils = require("@procore/web-sdk-mfe-utils");
1807
- var AI_TRANSLATION_FEATURE_FLAG_KEY = "pe_ai_translations_web_program_visible";
1903
+ var AI_TRANSLATION_FEATURE_FLAG_KEY = "ai-translation";
1808
1904
  var getAITranslationLDId = (domain) => {
1809
1905
  const { environment, zone } = (0, import_web_sdk_mfe_utils.getProcoreZone)(domain);
1810
1906
  if ((0, import_web_sdk_mfe_utils.isFederalZone)(zone)) {
@@ -1827,6 +1923,7 @@ var getAITranslationLDId = (domain) => {
1827
1923
  AITranslationProvider,
1828
1924
  AI_TRANSLATION_FEATURE_FLAG_KEY,
1829
1925
  BUTTON_TYPE,
1926
+ CustomizableAITranslateText,
1830
1927
  buildAnalyticEvent,
1831
1928
  buildEventKey,
1832
1929
  buildObject,
@@ -1201,12 +1201,13 @@ var ChromeTranslator = class _ChromeTranslator {
1201
1201
  };
1202
1202
  } catch (error) {
1203
1203
  const message = error instanceof Error ? error.message : "Unknown error";
1204
+ const isPermanentFailure = error instanceof DOMException && error.name === "NotSupportedError";
1204
1205
  return {
1205
1206
  translation: text,
1206
1207
  sourceLanguage: targetLanguage,
1207
1208
  success: false,
1208
1209
  errorMessage: message,
1209
- retryable: true
1210
+ retryable: !isPermanentFailure
1210
1211
  };
1211
1212
  }
1212
1213
  }
@@ -1776,9 +1777,93 @@ var AITranslateText = ({
1776
1777
  ] });
1777
1778
  };
1778
1779
 
1780
+ // src/components/CustomizableAITranslateText.tsx
1781
+ import React4, { useContext as useContext4, useEffect as useEffect3, useMemo, useRef as useRef3, useState as useState3 } from "react";
1782
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
1783
+ var CustomizableAITranslateText = ({
1784
+ text,
1785
+ shouldTranslate,
1786
+ showHighlight = false,
1787
+ segmenter,
1788
+ translatedIconProps
1789
+ }) => {
1790
+ const context = useContext4(AITranslationContext);
1791
+ const ait = context?.ait;
1792
+ const segments = useMemo(
1793
+ () => segmenter ? segmenter(text) : [{ text, translate: shouldTranslate }],
1794
+ [segmenter, text, shouldTranslate]
1795
+ );
1796
+ const [displayTexts, setDisplayTexts] = useState3(
1797
+ () => segments.map((s) => s.text)
1798
+ );
1799
+ const segmentsKey = JSON.stringify(segments);
1800
+ useEffect3(() => {
1801
+ setDisplayTexts(segments.map((s) => s.text));
1802
+ }, [segmentsKey]);
1803
+ useEffect3(() => {
1804
+ if (!ait || !shouldTranslate) {
1805
+ setDisplayTexts(segments.map((s) => s.text));
1806
+ return;
1807
+ }
1808
+ let cancelled = false;
1809
+ Promise.all(
1810
+ segments.map((s) => s.translate ? ait(s.text) : Promise.resolve(s.text))
1811
+ ).then((resolved) => {
1812
+ if (cancelled) return;
1813
+ setDisplayTexts(resolved);
1814
+ }).catch(() => {
1815
+ if (cancelled) return;
1816
+ setDisplayTexts(segments.map((s) => s.text));
1817
+ });
1818
+ return () => {
1819
+ cancelled = true;
1820
+ };
1821
+ }, [ait, shouldTranslate, segmentsKey]);
1822
+ const eventHandlerRef = useRef3(null);
1823
+ useEffect3(() => {
1824
+ if (!context) return;
1825
+ if (!eventHandlerRef.current) {
1826
+ eventHandlerRef.current = new EventHandler(context.tool);
1827
+ }
1828
+ const unsubscribe = eventHandlerRef.current.subscribeToRerenderEvent(
1829
+ async (sourceTexts) => {
1830
+ if (!shouldTranslate) return;
1831
+ if (!sourceTexts || sourceTexts.length === 0) return;
1832
+ const sourceSet = new Set(sourceTexts);
1833
+ const targets = segments.map((s, i) => ({ s, i })).filter(({ s }) => s.translate && sourceSet.has(s.text));
1834
+ if (targets.length === 0) return;
1835
+ const fresh = await Promise.all(
1836
+ targets.map(({ s }) => context.ait(s.text))
1837
+ );
1838
+ setDisplayTexts((prev) => {
1839
+ const next = [...prev];
1840
+ targets.forEach(({ i }, j) => {
1841
+ const value = fresh[j];
1842
+ if (value !== void 0) next[i] = value;
1843
+ });
1844
+ return next;
1845
+ });
1846
+ }
1847
+ );
1848
+ return () => unsubscribe();
1849
+ }, [context, shouldTranslate, segmentsKey]);
1850
+ const changedFlags = segments.map(
1851
+ (s, i) => s.translate && shouldTranslate && (displayTexts[i] ?? s.text) !== s.text
1852
+ );
1853
+ const anyChanged = changedFlags.some(Boolean);
1854
+ const showCellIcon = showHighlight && anyChanged;
1855
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
1856
+ showCellIcon && /* @__PURE__ */ jsx4(TranslatedIcon, { ...translatedIconProps }),
1857
+ segments.map((seg, i) => {
1858
+ const display = displayTexts[i] ?? seg.text;
1859
+ return /* @__PURE__ */ jsx4(React4.Fragment, { children: display }, `${i}-${seg.text}`);
1860
+ })
1861
+ ] });
1862
+ };
1863
+
1779
1864
  // src/utils/featureFlag.ts
1780
1865
  import { isFederalZone, getProcoreZone } from "@procore/web-sdk-mfe-utils";
1781
- var AI_TRANSLATION_FEATURE_FLAG_KEY = "pe_ai_translations_web_program_visible";
1866
+ var AI_TRANSLATION_FEATURE_FLAG_KEY = "ai-translation";
1782
1867
  var getAITranslationLDId = (domain) => {
1783
1868
  const { environment, zone } = getProcoreZone(domain);
1784
1869
  if (isFederalZone(zone)) {
@@ -1800,6 +1885,7 @@ export {
1800
1885
  AITranslationProvider,
1801
1886
  AI_TRANSLATION_FEATURE_FLAG_KEY,
1802
1887
  BUTTON_TYPE,
1888
+ CustomizableAITranslateText,
1803
1889
  buildAnalyticEvent,
1804
1890
  buildEventKey,
1805
1891
  buildObject,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@procore/ai-translations",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "Library that provides a solution to use AI to translate text into a language",
5
5
  "main": "dist/legacy/index.js",
6
6
  "types": "dist/legacy/index.d.ts",