npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@procore/ai-translation-utils

v0.3.3

Published

Utility functions for AI translation services

Maintainers

dancingshelldancingshelljustinmwattsjustinmwattsantonyayoubantonyayoubrysmithprocorerysmithprocorerobbiegprocorerobbiegprocorejadamsssjadamsssjeremy.bouzigardjeremy.bouzigardjgentesjgentesfaraz.haniffaraz.haniftimdohertytimdohertyajaykumar-procoreajaykumar-procoreb.bookoutb.bookoutjalyngjalyngchadryderchadryderhtaelhtaelrefaiepcnrefaiepcnjames.lawsonjames.lawsonvinayakaprabhuvinayakaprabhudavidshuredavidshurejames.clearyjames.clearyjl4everjl4everandersonbispoprocoreandersonbispoprocoredev-account-admindev-account-adminbrockpcorbrockpcorrowan.ibrahimrowan.ibrahimsseanwangsseanwangramysaid2ramysaid2vinaya-procorevinaya-procorelalovar-procorelalovar-procorebhargavrndbhargavrndihor.diachenko_procoreihor.diachenko_procorefarismmkfarismmkgideon-procoregideon-procoredannyporrellodannyporrelloalanprocorealanprocorechance.eakin.procorechance.eakin.procorestevenliprocorestevenliprocorejavio-procorejavio-procorekani-procorekani-procoreenyagaenyagadanny.oudanny.oumessanjahmessanjahdavid-christensen-procoredavid-christensen-procoreshradha.khardshradha.khardwinson.chuwinson.chueyvettesoueyvettesoulzhou888lzhou888jnhoang1jnhoang1nickprocorenickprocoreneil.mckeemanneil.mckeemanpam-whisenhuntpam-whisenhuntjgee67jgee67youssefameryoussefamermike-arndt-procoremike-arndt-procorebob.laskowskibob.laskowskicagmzcagmzmariah_delaneymariah_delaneylukenispellukenispelfabriciobdfabriciobdbikash.sahoobikash.sahoobbreyel921bbreyel921kimhin267kimhin267andy.mayerandy.mayerphil.custerphil.custerelijah.procoreelijah.procorejuliana.hernandezjuliana.hernandezjudy-lu-pcjudy-lu-pcprocore-it-supportprocore-it-supportandrewburke-pcandrewburke-pcjkleintechjkleintechrachel.arkebauerrachel.arkebauerprocore-npm-botprocore-npm-botgrafffffffgrafffffffyoyis3000yoyis3000james.dabbs-procorejames.dabbs-procorelaurenbrandsteinprocorelaurenbrandsteinprocorescottbieser-procorescottbieser-procoreamir-iskanderamir-iskanderzach.mckenzie.procorezach.mckenzie.procoreamyprocoreamyprocoreshayonj_procoreshayonj_procoreheplayskeysheplayskeysmike.southmike.souththomasoboylethomasoboyledischordedischordederek-carter-procorederek-carter-procoredlgasserdlgassercfprocorecfprocoreevan.waitsevan.waitsjeremy-marcusjeremy-marcusjmejia-fsljmejia-fslersgonzaloersgonzalotimofeeetimofeeestephan-procorestephan-procorealeclarsenprocorealeclarsenprocoresarah.freitassarah.freitasyihai.zweifelyihai.zweifeljay-rajanjay-rajanjacky-leijacky-leiapcarroll_procoreapcarroll_procoreprocore_halzyprocore_halzymehrdad-panahandehmehrdad-panahandehpeter.jinpeter.jinuddhavjoglekaruddhavjoglekarbrookyboy009brookyboy009denzylbalramdenzylbalramchangprocorechangprocoreallenanle.procoreallenanle.procoredevin.cunningham.procoredevin.cunningham.procoreari-procoreari-procorenoor.alinoor.alihgouhierprocorehgouhierprocorecyrille.baicyrille.baibrad.uranibrad.uranidmccraw-procoredmccraw-procorepatrick.lardinpatrick.lardinabhijit.patwardhanabhijit.patwardhanmatt.harris0223matt.harris0223alan.bresanialan.bresanijesse.olsenjesse.olsendtorres-procoredtorres-procoredineshkumar.jayakdineshkumar.jayakjason-kayejason-kayeyadhu.prakashyadhu.prakashleandro-procleandro-procandrew.wheelerandrew.wheelersherylnapigkitsherylnapigkitlydiaharalydiaharakahliholmeskahliholmessateesh-kadiyala-procoresateesh-kadiyala-procoreepalinprocoreepalinprocoredennis.heckmandennis.heckmanjamie-dugan-procorejamie-dugan-procoreviktoriia_azarovskaviktoriia_azarovskadaniel.ferreira-contractordaniel.ferreira-contractorwillpankonienwillpankonienladavargaladavargasteven.hinklesteven.hinkletxin1txin1chris.berberchris.berberetokarevetokarevritchleeritchleekarina.mendez-contractorkarina.mendez-contractorworldofsatyakiworldofsatyakigreg.sparksgreg.sparkskyle.williamskyle.williamskuldeepsingh4556kuldeepsingh4556jeremy.lundjeremy.lundbrocktillotsonprocorebrocktillotsonprocorestajicsstajicsryanfuentesprocoreryanfuentesprocoretyler.wasden.procoretyler.wasden.procorefabiomelo513fabiomelo513cody_schindler_procorecody_schindler_procoreamit.gurav-contractoramit.gurav-contractoryoasyo25yoasyo25kalyani.gosavikalyani.gosavihectorthielehectorthieleandersontr15andersontr15vishal-procorevishal-procoreomar.wagdyomar.wagdyyogevfine1yogevfine1charan_procorecharan_procorescorgiat-procorescorgiat-procorembartlett413mbartlett413attachiattachiahmed.ghorabahmed.ghorabvaromirvaromiralyelashram_procorealyelashram_procoreilya.dryha-contractorilya.dryha-contractorevan.cerwonka.procoreevan.cerwonka.procorevsobol-cvsobol-cdmitri_wmdmitri_wmkellikearnskellikearnsrichard.bunnrichard.bunnchaitra-m-15chaitra-m-15conner-procoreconner-procoremishaelowoyemimishaelowoyemipeterknifpeterknifaleh.haurylenia-contractoraleh.haurylenia-contractormiguel.garcia-procoremiguel.garcia-procorecodyrobertsprocorecodyrobertsprocorea.elbadaweia.elbadaweilnspatz914lnspatz914melch-procoremelch-procoremustafa-abdelrahmanmustafa-abdelrahmanatoaimaatoaimajasaswinijasaswiniadarsh.gautamadarsh.gautamamin.jaipuriamin.jaipurimax.helmetagmax.helmetags_kudryks_kudrykhyogmanhyogmankyle.liukyle.liudavidkangprodavidkangprostevenkang3stevenkang3cbathgatecbathgatevictorbendeck-pcvictorbendeck-pcsarah.herediasarah.herediamoaz-ashrafmoaz-ashrafaly-el-kerdanyaly-el-kerdanyprocore-oss-userprocore-oss-userabhishekkumar123abhishekkumar123stephanie.breretonstephanie.breretonsaurasumprocoresaurasumprocoremona.khairbekmona.khairbekelewando-procoreelewando-procorejyang-procorejyang-procoretedyangtedyangdeiabdeiabjgreene_procorejgreene_procoreasamayasamaykenny.foisykenny.foisyganesh.raghupathyganesh.raghupathyrajatmenhdirattarajatmenhdirattayzhou2024yzhou2024dlameter-procoredlameter-procoredecha-sansondecha-sansonkylepietzkylepietzconnie-feng-procoreconnie-feng-procoreroger-procoreroger-procorematheusprocorematheusprocorefernandocamilottifernandocamilottisimona.iancusimona.iancujacksonleach-procorejacksonleach-procoreg2mitchellg2mitchelltatsiana.cliftontatsiana.cliftonphunguyen-pcorphunguyen-pcorpmfrawleypmfrawleybrian.smith1brian.smith1scottsternscottsternneil1023neil1023srichaitanya.peddintisrichaitanya.peddintijake-pitkinjake-pitkinerikthoresonerikthoresonlhuang325lhuang325abhijit-procoreabhijit-procorerodayna.ehabrodayna.ehabfairchildfairchildmustafa-u-abdelrahmanmustafa-u-abdelrahmanaberkowitzaberkowitzpwhisenhunt-procorepwhisenhunt-procoremariia.solodovnikmariia.solodovniknigeld-procorenigeld-procoresamad.viranisamad.viranibohdan-horai-procorebohdan-horai-procoremathenes_procoremathenes_procorevinoth.kuppusamyvinoth.kuppusamyzayterzayteralan.facchini-contractoralan.facchini-contractorcassianomatos-procorecassianomatos-procoreamitk030amitk030sflang-procoresflang-procoretracy.ottotracy.ottodaniel-pierre-procoredaniel-pierre-procoreglidenorglidenorashish.sharma2024ashish.sharma2024gaurav.sharma.procoregaurav.sharma.procoreandres-mendez-procoreandres-mendez-procoreroobo-romeskiroobo-romeskikylemartinez-procorekylemartinez-procoresean.spearman.procoresean.spearman.procoregturkadzegturkadzejeffgiaquintojeffgiaquintoezrasimeloffezrasimeloffbill-wagnerbill-wagnerkellen.stewartkellen.stewartrodrigo.dejuanarodrigo.dejuanasaranahal2saranahal2andrew.isaacandrew.isaacagamaleldinagamaleldinmostafaeltazymostafaeltazymagdyyxxmagdyyxxandreszorrilla-procoreandreszorrilla-procoremohitsharma97mohitsharma97tejeshwartejeshwarswati.jadhavswati.jadhavsquidbeakssquidbeakssmishra06smishra06subham.panigrahisubham.panigrahideepak.kumartsdeepak.kumartsvaibhav6521vaibhav6521bagnaram-procorebagnaram-procoremahesh-s96mahesh-s96mohamed.adelmohamed.adelnubs-procorenubs-procorerana.eltayarrana.eltayarmahmoud-sharsharmahmoud-sharsharsyamphanindrasyamphanindraveroniaosamaveroniaosamaimanselimimanselimhelmy162-procorehelmy162-procorepclemonspclemonssamuelvelez8383samuelvelez8383vinitdeshkar-procorevinitdeshkar-procoremariam_mazenmariam_mazenmina-elnagarmina-elnagardaniel_andrewsdaniel_andrewsmohanad-aymanmohanad-aymanarsenii.derkach-procorearsenii.derkach-procorestepanvanzuriakprocorestepanvanzuriakprocorend-procorend-procoremarwansalem-prcmarwansalem-prcyoussefothmanyoussefothmanandrii.datsenko-contractorandrii.datsenko-contractor

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

Installation

yarn add @procore/ai-translation-utils

Hello 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 (reads columnFeatureState)
    ├── TranslationProgressPopup.tsx
    ├── ModelDownloadProgressPopup.tsx
    └── popupShared.tsx        # Internal: shared styles, ProgressBar, ErrorBoundary

Class & 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~~
        +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

API Reference

Exported Types & Interfaces

| 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 |


Column Feature State Functions

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. |


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 Renderer & 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 '' |


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 cell

Grid 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 columns

Type Safety & Generics

WeakMap State Isolation (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:

  1. Key type constraint -- Only 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.
  2. 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.
  3. Automatic cleanup -- 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.

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 .current property is always typed as GridApi | null
  • The ref object itself is stable across renders (referential equality)
  • Action closures in menu options always read the latest GridApi via 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 exports support) resolve via dist/modern/.
  • Legacy consumers fall back to dist/legacy/ via the main, module, and types fields.
  • 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
├── 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 values
  • columnFeatureState is mocked in aiMenuOptions.test.ts to isolate the factory logic

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