@procore/ai-translation-utils
v0.4.0
Published
Utility functions for AI translation services
Maintainers
Keywords
Readme
@procore/ai-translation-utils
AG Grid data-table integration utilities for [@procore/ai-translations](https://github.com/procore/platform-internationalization-js-monorepo). Provides type-safe column-level translation toggling, cell rendering, progress popups, and menu option factories -- all wired through AG Grid's GridApi and React refs.
Table of Contents
- Quick Start
- Core Architecture
- API Reference
- Workflow: Request Flow
- Type Safety & Generics
- Advanced Patterns
- Developer Experience
- Peer Dependencies
- License
Quick Start
Installation
yarn add @procore/ai-translation-utilsHello World -- A Fully Typed AG Grid with AI Translation
import React, { useRef } from 'react';
import type { GridApi, ColDef } from '@ag-grid-community/core';
import { AgGridReact } from '@ag-grid-community/react';
import { AITranslationProvider } from '@procore/ai-translations';
import {
handleGridReady,
createAIMenuOptions,
TranslatableCell,
TranslationProgressPopup,
ModelDownloadProgressPopup,
type AIMenuOption,
} from '@procore/ai-translation-utils';
export function TranslatableGrid() {
const gridApiRef = useRef<GridApi | null>(null);
const aiMenuOptions: AIMenuOption[] = createAIMenuOptions(gridApiRef, {
translate: 'Translate Column',
highlight: 'Highlight Translations',
});
const columnDefs: ColDef[] = [
{
field: 'description',
cellRenderer: TranslatableCell,
headerComponentParams: { menuOptions: aiMenuOptions },
},
];
return (
<AITranslationProvider>
<AgGridReact
columnDefs={columnDefs}
rowData={[{ description: 'Hello, world!' }]}
onGridReady={(params) => handleGridReady(params, gridApiRef)}
/>
<TranslationProgressPopup />
<ModelDownloadProgressPopup />
</AITranslationProvider>
);
}Every export above is explicitly typed. gridApiRef is MutableRefObject<GridApi | null>, aiMenuOptions is AIMenuOption[], and columnDefs uses AG Grid's ColDef -- the compiler enforces correctness at every integration point.
Core Architecture
The package is organized into a single data-table module that exposes three layers: state management, UI components, and a menu factory.
Module Structure
src/
├── index.ts # Barrel: re-exports everything from data-table/
└── data-table/
├── index.ts # Public API barrel
├── columnFeatureState.ts # WeakMap-backed per-grid, per-column state
├── aiMenuOptions.ts # Menu option factory (depends on columnFeatureState)
├── TranslatableCell.tsx # AG Grid cell renderer — translates the whole cell value
├── CustomizableTranslatableCell.tsx # AG Grid cell renderer — translates chosen substrings via a segmenter
├── TranslationProgressPopup.tsx
├── ModelDownloadProgressPopup.tsx
└── popupShared.tsx # Internal: shared styles, ProgressBar, ErrorBoundaryClass & Interface Diagram
classDiagram
direction LR
class AIMenuOption {
<<interface>>
+label: string
+value: string
+action(colDef: ColDef | null) void
}
class columnFeatureState {
<<module>>
-translationStateMap: WeakMap~GridApi, Record~string, boolean~~
-highlightStateMap: WeakMap~GridApi, Record~string, boolean~~
-highlightModeMap: WeakMap~GridApi, Record~string, HighlightMode~~
+isColumnTranslationEnabled(gridApi, field) boolean
+toggleColumnTranslation(gridApi, field) void
+isColumnHighlightEnabled(gridApi, field) boolean
+toggleColumnHighlight(gridApi, field) void
+getColumnHighlightMode(gridApi, field) HighlightMode
+setColumnHighlightMode(gridApi, field, mode) void
+clearColumnFeatureStates(gridApi) void
+handleGridReady(params, gridApiRef) void
}
class createAIMenuOptions {
<<function>>
+createAIMenuOptions(gridApiRef, labels) AIMenuOption[]
}
class TranslatableCell {
<<component>>
+TranslatableCell(params: ICellRendererParams) JSX.Element
+extractText(value: unknown) string
}
class CustomizableTranslatableCell {
<<component>>
+CustomizableTranslatableCell(props: ICellRendererParams and CustomizableTranslatableCellParams) JSX.Element
}
class TranslationProgressPopup {
<<component>>
+label? string
+TranslationProgressPopup(props) JSX.Element
}
class ModelDownloadProgressPopup {
<<component>>
+label? string
+ModelDownloadProgressPopup(props) JSX.Element
}
class ProgressErrorBoundary {
<<internal>>
+state: hasError boolean
+getDerivedStateFromError() state
+render() ReactNode | null
}
createAIMenuOptions --> columnFeatureState : calls toggle functions
createAIMenuOptions --> AIMenuOption : returns
TranslatableCell --> columnFeatureState : reads state
TranslatableCell --> AITranslateText : delegates rendering
CustomizableTranslatableCell --> columnFeatureState : reads state
CustomizableTranslatableCell --> CustomizableAITranslateText : delegates rendering
TranslationProgressPopup --> ProgressErrorBoundary : wrapped by
ModelDownloadProgressPopup --> ProgressErrorBoundary : wrapped by
TranslationProgressPopup --> useAITranslation : reads translationProgress
ModelDownloadProgressPopup --> useAITranslation : reads modelDownloadProgressAPI Reference
Exported Types & Interfaces
| Type / Interface | Source | Properties / Signature | Description |
| ------------------------------------ | ---------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| AIMenuOption | aiMenuOptions.ts | label: string, value: string, action: (colDef: ColDef \| null) => void | Describes a single menu entry for AG Grid column headers. action receives the column definition so toggle logic can identify which column to operate on. |
| TranslationProgressPopupProps | TranslationProgressPopup.tsx | label?: string | Optional props for TranslationProgressPopup. label overrides the default "Translating..." text. |
| ModelDownloadProgressPopupProps | ModelDownloadProgressPopup.tsx | label?: string | Optional props for ModelDownloadProgressPopup. label overrides the default "Downloading AI Model..." text. |
| TextSegmenter | @procore/ai-translations | (text: string) => TranslatableSegment[] | Pure function that splits an extracted cell string into ordered translatable / non-translatable segments. Import directly from @procore/ai-translations and pass via cellRendererParams.segmenter. |
| TranslatableSegment | @procore/ai-translations | text: string, translate: boolean | A single segment returned by a TextSegmenter. When translate is true the segment goes through the AI translation pipeline; otherwise it is rendered as plain text. Import from @procore/ai-translations. |
| HighlightMode | @procore/ai-translations | 'segment' \| 'cell' | Where the translation highlight icon appears. Import from @procore/ai-translations. |
| CustomizableTranslatableCellParams | CustomizableTranslatableCell.tsx | segmenter?: TextSegmenter, highlightMode?: HighlightMode | cellRendererParams shape for CustomizableTranslatableCell. highlightMode is a static initial value; for dynamic runtime changes use setColumnHighlightMode. |
Dependency types used in public signatures (from peer packages, not re-exported):
| Type | Source Package | Usage |
| --------------------- | ------------------------- | --------------------------------------------------- |
| GridApi | @ag-grid-community/core | Grid instance passed to all column state functions |
| GridReadyEvent | @ag-grid-community/core | Event parameter for handleGridReady |
| ColDef | @ag-grid-community/core | Column definition received by AIMenuOption.action |
| ICellRendererParams | @ag-grid-community/core | Props received by TranslatableCell |
| MutableRefObject<T> | react | React ref holding GridApi \| null |
Column Feature State Functions
All state is stored in module-scoped WeakMap instances keyed by GridApi. State is automatically garbage-collected when a GridApi is disposed and is isolated per grid instance.
| Function | Signature | Returns | Description |
| ---------------------------- | -------------------------------------------------------------------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| isColumnTranslationEnabled | (gridApi: GridApi \| null \| undefined, field: string \| undefined) => boolean | boolean | Returns true if translation is active for field. Safely returns false for nullish inputs. |
| toggleColumnTranslation | (gridApi: GridApi \| null \| undefined, field: string \| undefined) => void | void | Flips the translation flag for field and calls gridApi.refreshCells(). No-op if gridApi, field, or the column definition is missing. |
| isColumnHighlightEnabled | (gridApi: GridApi \| null \| undefined, field: string \| undefined) => boolean | boolean | Returns true if highlighting is active for field. |
| toggleColumnHighlight | (gridApi: GridApi \| null \| undefined, field: string \| undefined) => void | void | Flips the highlight flag for field and refreshes cells. |
| getColumnHighlightMode | (gridApi: GridApi \| null \| undefined, field: string \| undefined) => HighlightMode | HighlightMode | Returns the current highlight mode for field. Defaults to 'segment' when not explicitly set. |
| setColumnHighlightMode | (gridApi: GridApi \| null \| undefined, field: string \| undefined, mode: HighlightMode) => void | void | Sets the highlight mode for field and refreshes cells. Use this instead of cellRendererParams to avoid grid re-initialization on mode changes. |
| clearColumnFeatureStates | (gridApi: GridApi \| null \| undefined) => void | void | Resets all state maps (translation, highlight, highlight mode) for the grid. Refreshes only columns that had active states. |
| handleGridReady | (params: GridReadyEvent, gridApiRef: MutableRefObject<GridApi \| null>) => void | void | Stores params.api in the ref and registers a paginationChanged listener that auto-clears feature states. |
Menu Options Factory
| Function | Signature | Returns |
| --------------------- | --------------------------------------------------------------------------------------------------------------------- | ---------------- |
| createAIMenuOptions | (gridApiRef: MutableRefObject<GridApi \| null>, labels: { translate: string; highlight: string }) => AIMenuOption[] | AIMenuOption[] |
Returns two AIMenuOption entries (translate and highlight). Each option's action closure captures the gridApiRef and calls the corresponding toggle function from columnFeatureState. The labels parameter allows localization of menu text.
const options = createAIMenuOptions(gridApiRef, {
translate: 'Traducir columna', // Spanish
highlight: 'Resaltar traducciones',
});Cell Renderers & Utilities
TranslatableCell
(params: ICellRendererParams) => JSX.Element;AG Grid cell renderer component. Reads column-level translation/highlight state from the GridApi on every render, extracts display text via extractText, and delegates to AITranslateText from @procore/ai-translations. Uses React.memo internally to avoid unnecessary re-renders of the translation component.
extractText
(value: unknown) => string;Coerces an AG Grid cell value into a display string:
| Input Type | Behavior |
| -------------------------------------- | ------------------------------------------------------------------------------------------------- |
| string | Returned as-is |
| Array<{ label?: string } \| unknown> | Each element: uses .label if object with label key, otherwise String(v); joined with ', ' |
| { label?: string } | Returns .label ?? '' |
| Anything else | Returns '' |
CustomizableTranslatableCell
(props: ICellRendererParams & CustomizableTranslatableCellParams) =>
JSX.Element;AG Grid cell renderer for cases where only part of the cell value should be translated. Thin adapter over CustomizableAITranslateText from @procore/ai-translations — does three jobs:
- Reads per-column translate/highlight state (and highlight mode) from
columnFeatureStateso the header-menu toggles continue to work. - Extracts the display string from
params.valueviaextractText. - Passes the consumer's
segmenterdirectly toCustomizableAITranslateText.
If you need the same "translate part of this string" behaviour outside a data table (forms, cards, descriptions, etc.), reach for CustomizableAITranslateText directly in @procore/ai-translations.
| cellRendererParams field | Type | Description |
| -------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| segmenter | TextSegmenter (optional) | Pure function (text: string) => TranslatableSegment[]. Splits the extracted cell string into translatable and non-translatable parts. When omitted, the whole extracted value is translated (same behaviour as TranslatableCell). |
| highlightMode | HighlightMode (optional) | Static initial value for where the highlight icon appears ('segment' or 'cell'). Use this when the mode is fixed at column-definition time and will never change at runtime. For dynamic post-mount changes, call setColumnHighlightMode(gridApi, field, mode) instead — it updates the WeakMap and refreshes cells without recreating the column definition, which would reset all toggle state. |
Types
import type {
TextSegmenter,
TranslatableSegment,
HighlightMode,
} from '@procore/ai-translations';
type CustomizableTranslatableCellParams = {
segmenter?: TextSegmenter;
highlightMode?: HighlightMode;
};Example: translating only the label in "{code} - {label}"
import {
CustomizableTranslatableCell,
setColumnHighlightMode,
handleGridReady,
type TextSegmenter,
} from '@procore/ai-translation-utils';
import { useRef } from 'react';
const codeLabelSegmenter: TextSegmenter = (text) => {
const idx = text.indexOf(' - ');
if (idx === -1) return [{ text, translate: true }];
return [
{ text: text.slice(0, idx + 3), translate: false },
{ text: text.slice(idx + 3), translate: true },
];
};
function MyGrid() {
const gridApiRef = useRef(null);
// Set highlight mode once after the grid is ready
const onGridReady = (params) => {
handleGridReady(params, gridApiRef);
setColumnHighlightMode(params.api, 'item', 'cell'); // one icon per cell
};
const columnDefs = [
{
field: 'item',
cellRenderer: CustomizableTranslatableCell,
cellRendererParams: { segmenter: codeLabelSegmenter },
columnHeaderParams: { menuOptions: aiMenuOptions },
},
];
return <AgGridReact columnDefs={columnDefs} onGridReady={onGridReady} />;
}Example: key-value split "Status: Awaiting Review"
const colonSplitSegmenter: TextSegmenter = (text) => {
const idx = text.indexOf(': ');
if (idx === -1) return [{ text, translate: true }];
return [
{ text: text.slice(0, idx + 2), translate: false }, // "Status: " — keep
{ text: text.slice(idx + 2), translate: true }, // "Awaiting Review" — translate
];
};
cellRendererParams: {
segmenter: colonSplitSegmenter;
}Progress Popups
Both components consume the useAITranslation() hook from @procore/ai-translations and render fixed-position popups. Each is wrapped in a ProgressErrorBoundary that renders null on error, preventing translation UI failures from crashing the host app.
| Component | Hook Data | Visibility | Progress Bar Color |
| ---------------------------- | ----------------------- | ----------------------------------- | ------------------ |
| TranslationProgressPopup | translationProgress | Hidden when null or total === 0 | #f47e42 (orange) |
| ModelDownloadProgressPopup | modelDownloadProgress | Hidden when null or isComplete | #3b82f6 (blue) |
Props
Both components accept an optional label prop. When omitted, the default label is used.
| Prop | Type | Required | Default (TranslationProgressPopup) | Default (ModelDownloadProgressPopup) |
| ------- | -------- | -------- | ---------------------------------- | ------------------------------------ |
| label | string | No | "Translating..." | "Downloading AI Model..." |
// Default labels
<TranslationProgressPopup />
<ModelDownloadProgressPopup />
// Custom labels (e.g. for localization)
<TranslationProgressPopup label="Traduciendo..." />
<ModelDownloadProgressPopup label="Descargando modelo..." />Workflow: Request Flow
The following sequence diagram shows how a user toggling "Translate Column" from a column header menu flows through the system:
sequenceDiagram
participant User
participant AGGrid as AG Grid Column Menu
participant Factory as createAIMenuOptions
participant State as columnFeatureState
participant WeakMap as WeakMap<GridApi, Record>
participant Cell as TranslatableCell
participant AIText as AITranslateText
Note over Factory: At mount time, factory creates menu options
Factory->>State: Captures toggleColumnTranslation in action closure
User->>AGGrid: Clicks "Translate Column"
AGGrid->>Factory: Invokes action(colDef)
Factory->>State: toggleColumnTranslation(gridApi, colDef.field)
State->>WeakMap: Flip field flag in translationStateMap
State->>AGGrid: gridApi.refreshCells({ columns: [field], force: true })
AGGrid->>Cell: Re-renders TranslatableCell(params)
Cell->>State: isColumnTranslationEnabled(api, field) → true
Cell->>State: isColumnHighlightEnabled(api, field)
Cell->>Cell: extractText(params.value) → display string
Cell->>AIText: <AITranslateText text shouldTranslate showHighlight />
AIText-->>User: Translated text rendered in cellGrid Initialization Flow
sequenceDiagram
participant App
participant AGGrid as AG Grid
participant Handler as handleGridReady
participant Ref as gridApiRef (MutableRefObject)
participant State as columnFeatureState
AGGrid->>Handler: onGridReady(params)
Handler->>Ref: gridApiRef.current = params.api
Handler->>AGGrid: api.addEventListener('paginationChanged', callback)
Note over AGGrid: User navigates to page 2
AGGrid->>State: paginationChanged → clearColumnFeatureStates(gridApi)
State->>State: Reset both WeakMaps for this grid
State->>AGGrid: refreshCells for previously active columnsType Safety & Generics
WeakMap State Isolation (columnFeatureState.ts)
The central design decision in columnFeatureState.ts is using WeakMap instances keyed by GridApi for all per-column runtime state:
const translationStateMap = new WeakMap<GridApi, Record<string, boolean>>();
const highlightStateMap = new WeakMap<GridApi, Record<string, boolean>>();
const highlightModeMap = new WeakMap<GridApi, Record<string, HighlightMode>>();Keeping highlightMode in the WeakMap (rather than in cellRendererParams) is important: if highlightMode were part of cellRendererParams, the column definition would change whenever the mode changed, causing AG Grid to re-initialize and silently lose all toggle state stored in the other two WeakMaps.
This delivers three type-safety guarantees:
- Key type constraint -- Only
GridApiinstances can be used as keys. Passing a plain object ornullis a compile-time error at theWeakMaplevel; the exported functions add runtime guards (if (!gridApi) return) for thenull | undefinedunion that consumers pass. - Value type contract --
Record<string, boolean>enforces that column field names (string) always map to boolean flags. This prevents accidental storage of richer objects or numeric states. - Automatic cleanup --
WeakMapallows the JavaScript engine to garbage-collect entries when theGridApiinstance is no longer referenced. This is critical in SPAs where grids mount/unmount; no manual teardown is needed.
Nullable Parameter Patterns
Every public function accepts GridApi | null | undefined and string | undefined rather than requiring non-null values. This is deliberate -- it matches how AG Grid hands back these values in practice (e.g., colDef?.field is string | undefined, and a ref starts as null). The functions guard internally, so consumers don't need to litter their code with null checks:
// No need for: if (gridApi && field) { toggleColumnTranslation(gridApi, field); }
// Just call it directly -- the function handles nullish values safely:
toggleColumnTranslation(gridApiRef.current, colDef?.field);MutableRefObject<GridApi | null>
The createAIMenuOptions and handleGridReady functions accept MutableRefObject<GridApi | null> -- React's useRef return type. This ensures:
- The ref's
.currentproperty is always typed asGridApi | null - The ref object itself is stable across renders (referential equality)
- Action closures in menu options always read the latest
GridApivia the ref, avoiding stale closure bugs
AIMenuOption.action and ColDef
The action callback is typed as (colDef: ColDef | null) => void. The ColDef | null union accounts for cases where the menu is invoked without a column context. Implementations use optional chaining (colDef?.field) to safely extract the field name.
Advanced Patterns
Custom Menu Options
Extend the menu options array with your own entries that follow the AIMenuOption interface:
import type { AIMenuOption } from '@procore/ai-translation-utils';
import { createAIMenuOptions } from '@procore/ai-translation-utils';
const baseOptions = createAIMenuOptions(gridApiRef, {
translate: 'Translate',
highlight: 'Highlight',
});
const customOption: AIMenuOption = {
label: 'Export Translations',
value: 'export',
action: (colDef) => {
if (colDef?.field) {
exportTranslationsForColumn(colDef.field);
}
},
};
const allOptions: AIMenuOption[] = [...baseOptions, customOption];Custom Cell Renderer Wrapping extractText
Build on extractText to create a cell renderer with additional formatting:
import {
extractText,
isColumnTranslationEnabled,
} from '@procore/ai-translation-utils';
import type { ICellRendererParams } from '@ag-grid-community/core';
export function CustomTranslatableCell(params: ICellRendererParams) {
const field = params.colDef?.field;
const isTranslating = isColumnTranslationEnabled(params.api, field);
const text = extractText(params.value);
return (
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
{isTranslating && <TranslationIcon />}
<span>{text}</span>
</div>
);
}Multi-Grid State Isolation
Because state is keyed by GridApi via WeakMap, multiple grids on the same page are automatically isolated:
function DualGridPage() {
const gridApiRefA = useRef<GridApi | null>(null);
const gridApiRefB = useRef<GridApi | null>(null);
// Each grid gets its own independent translation/highlight state
const menuA = createAIMenuOptions(gridApiRefA, {
translate: 'Translate',
highlight: 'Highlight',
});
const menuB = createAIMenuOptions(gridApiRefB, {
translate: 'Translate',
highlight: 'Highlight',
});
return (
<>
<AgGridReact
onGridReady={(p) => handleGridReady(p, gridApiRefA)} /* ... */
/>
<AgGridReact
onGridReady={(p) => handleGridReady(p, gridApiRefB)} /* ... */
/>
</>
);
}Programmatic State Control
Bypass the menu UI to toggle translation from external controls:
import {
toggleColumnTranslation,
isColumnTranslationEnabled,
} from '@procore/ai-translation-utils';
function TranslateAllButton({
gridApiRef,
fields,
}: {
gridApiRef: MutableRefObject<GridApi | null>;
fields: string[];
}) {
const handleClick = () => {
for (const field of fields) {
if (!isColumnTranslationEnabled(gridApiRef.current, field)) {
toggleColumnTranslation(gridApiRef.current, field);
}
}
};
return <button onClick={handleClick}>Translate All Columns</button>;
}Developer Experience
Prerequisites
This package is part of the platform-internationalization-js-monorepo
Scripts
| Command | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| yarn build | Builds the package via hammer lib:build (tsup/ESBuild). Produces dist/modern/ (ESM + CJS with conditional exports) and dist/legacy/ (fallback entry points). |
| yarn dev | Starts a watch-mode dev build (hammer lib:start --env=development). |
| yarn test | Builds first, then runs Jest tests with coverage. |
| yarn test:nodeps | Runs tests without a preceding build (useful during development). |
| yarn test:watch | Runs tests in watch mode. |
| yarn lint | Runs both ESLint (lint:code) and TypeScript type checking (lint:types). |
| yarn format | Formats all files with Prettier. |
| yarn format:check | Checks formatting without writing changes. |
| yarn analyze | Builds with metafile output, then opens an interactive bundle visualizer. |
| yarn clean | Removes the dist/ directory. |
Build Output: dist/ vs src/
The src/ directory contains TypeScript source files. The build process (tsup via Hammer) compiles these into two output directories:
dist/
├── modern/ # Used by package.json "exports" field
│ ├── index.mjs # ESM bundle (import)
│ ├── index.js # CJS bundle (require)
│ ├── index.d.mts # ESM type declarations
│ └── index.d.ts # CJS type declarations
└── legacy/ # Used by package.json "main"/"module"/"types" fields
├── index.js # CJS entry
├── index.mjs # ESM entry
└── index.d.ts # Type declarations- Modern consumers (Node 16+, bundlers with
exportssupport) resolve viadist/modern/. - Legacy consumers fall back to
dist/legacy/via themain,module, andtypesfields. - Only the
dist/directory is published to npm ("files": ["dist"]).
Testing
Tests use Jest (via @procore/hammer-test-jest) with React Testing Library and jest-dom matchers. Test files live alongside source code in __tests__/ directories.
src/data-table/__tests__/
├── columnFeatureState.test.ts # Unit tests with mocked GridApi
├── aiMenuOptions.test.ts # Tests with mocked columnFeatureState
├── TranslatableCell.test.tsx # Component rendering tests
├── CustomizableTranslatableCell.test.tsx # Adapter wiring: segmenter passthrough, column state, highlight
├── TranslationProgressPopup.test.tsx # Popup visibility/progress tests
└── ModelDownloadProgressPopup.test.tsxKey testing patterns used:
GridApimethods are mocked via Jest (jest.fn()) since AG Grid is a peer dependency@procore/ai-translationsis mocked at the module level to isolate adapter logic from the translation pipelinecolumnFeatureStateis mocked in component tests soisColumnTranslationEnabled,isColumnHighlightEnabled, andgetColumnHighlightModereturn controlled values
Peer Dependencies
| Package | Version | Purpose |
| -------------------------- | -------- | ------------------------------------------------------------------------------------- |
| @ag-grid-community/core | >= 31 | AG Grid types and API (GridApi, ColDef, ICellRendererParams, GridReadyEvent) |
| @procore/ai-translations | >= 0.6.0 | AITranslateText component, useAITranslation hook, AITranslationProvider context |
| react | >= 17 | React runtime for components, hooks, and refs |
License
SEE LICENSE IN LICENSE
