
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
@procore/ai-translation-utils
Advanced tools
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.
yarn add @procore/ai-translation-utils
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.
The package is organized into a single data-table module that exposes three layers: state management, UI components, and a menu factory.
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 (reads columnFeatureState)
├── TranslationProgressPopup.tsx
├── ModelDownloadProgressPopup.tsx
└── popupShared.tsx # Internal: shared styles, ProgressBar, ErrorBoundary
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~~
+isColumnTranslationEnabled(gridApi, field) boolean
+toggleColumnTranslation(gridApi, field) void
+isColumnHighlightEnabled(gridApi, field) boolean
+toggleColumnHighlight(gridApi, field) 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 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
TranslationProgressPopup --> ProgressErrorBoundary : wrapped by
ModelDownloadProgressPopup --> ProgressErrorBoundary : wrapped by
TranslationProgressPopup --> useAITranslation : reads translationProgress
ModelDownloadProgressPopup --> useAITranslation : reads modelDownloadProgress
| Type / Interface | Source | Properties | 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. |
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 |
All state is stored in module-scoped WeakMap<GridApi, Record<string, boolean>> instances. This means 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. |
clearColumnFeatureStates | (gridApi: GridApi | null | undefined) => void | void | Resets both translation and highlight maps 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. |
| 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',
});
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 '' |
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) |
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..." />
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 cell
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 columns
columnFeatureState.ts)The central design decision in columnFeatureState.ts is using WeakMap<GridApi, Record<string, boolean>> for both translation and highlight state:
const translationStateMap = new WeakMap<GridApi, Record<string, boolean>>();
const highlightStateMap = new WeakMap<GridApi, Record<string, boolean>>();
This delivers three type-safety guarantees:
GridApi instances can be used as keys. Passing a plain object or null is a compile-time error at the WeakMap level; the exported functions add runtime guards (if (!gridApi) return) for the null | undefined union that consumers pass.Record<string, boolean> enforces that column field names (string) always map to boolean flags. This prevents accidental storage of richer objects or numeric states.WeakMap allows the JavaScript engine to garbage-collect entries when the GridApi instance is no longer referenced. This is critical in SPAs where grids mount/unmount; no manual teardown is needed.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:
.current property is always typed as GridApi | nullGridApi via the ref, avoiding stale closure bugsAIMenuOption.action and ColDefThe 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.
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];
extractTextBuild 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>
);
}
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)} /* ... */
/>
</>
);
}
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>;
}
This package is part of the platform-internationalization-js-monorepo
| 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. |
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
exports support) resolve via dist/modern/.dist/legacy/ via the main, module, and types fields.dist/ directory is published to npm ("files": ["dist"]).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
├── TranslationProgressPopup.test.tsx # Popup visibility/progress tests
└── ModelDownloadProgressPopup.test.tsx
Key testing patterns used:
GridApi methods are mocked via Jest (jest.fn()) since AG Grid is a peer dependency@procore/ai-translations is mocked at the module level to control hook return valuescolumnFeatureState is mocked in aiMenuOptions.test.ts to isolate the factory logic| 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 |
SEE LICENSE IN LICENSE
FAQs
Utility functions for AI translation services
The npm package @procore/ai-translation-utils receives a total of 170 weekly downloads. As such, @procore/ai-translation-utils popularity was classified as not popular.
We found that @procore/ai-translation-utils demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 303 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.