Advisor Inspector
The Advisor Inspector tool allows your app to contribute a check to the Crowdin Advisor and report insights about translation quality, glossary consistency, brand tone, or any other custom signal.
Sample
Section titled “Sample”const crowdinModule = require('@crowdin/app-project-module');const { reportAdvisorInsight } = crowdinModule;
// In-memory dedup: BE may fire multiple nudges within a short window// (install + project-create + user-recheck). Keep only one in flight.const inFlight = new Set();
const configuration = { baseUrl: 'https://123.ngrok.io', clientId: 'clientId', clientSecret: 'clientSecret', name: 'Sample App', identifier: 'sample-app', description: 'Sample App description', dbFolder: __dirname, imagePath: __dirname + '/' + 'logo.png', advisorInspector: [ { key: 'daily-llm-check', title: 'Daily LLM Check', summary: 'Looks at recent translations and flags anything suspicious.', severity: 'medium', category: 'context', refreshPolicy: 'daily', uiPath: __dirname + '/public', onRecheck: async ({ projectId, moduleKey, inspectorId, crowdinId }) => { const dedupKey = `${projectId}:${moduleKey}`; if (inFlight.has(dedupKey)) return; inFlight.add(dedupKey);
try { // ...simulate your async/queued check (could take minutes or hours)... await new Promise((resolve) => setTimeout(resolve, 30_000));
await reportAdvisorInsight(configuration, { crowdinId, projectId, moduleKey, outcome: 'flagged', metrics: [ { key: 'suspicious-strings', value: 7, unit: 'count', tone: 'danger' } ], recommendations: [ { id: 'review-recent-translations', primary: true } ], payload: { reviewedAt: new Date().toISOString() }, }); } finally { inFlight.delete(dedupKey); } } } ]};
crowdinModule.createApp(configuration);Configuration
Section titled “Configuration”| Parameter | Description | Allowed values | Default value |
|---|---|---|---|
key | Raw module key. Becomes the URL segment for the recheck/insight endpoints and the value of jwt.module. | String | derived from identifier |
title | Header shown in the Advisor card. | String | – |
summary | Optional summary line under the title. ≤70 chars. | String | – |
severity | Visual weight of the insight. | 'high' | 'medium' | 'low' | – |
category | Inspector category. | 'context' | – |
refreshPolicy | How often the BE may send recheck nudges. | 'hourly' | 'daily' | – |
onRecheck | Async callback invoked when Crowdin pushes a recheck nudge. The SDK answers 200 to Crowdin immediately; your callback runs in the background. | Function | – |
uiPath | Path to the iframe content folder (e.g. __dirname + '/public'). | String | – |
fileName | Entry filename inside uiPath. | String | 'index.html' |
formSchema | JSON Schema for an auto-generated form (alternative to uiPath). | Object | – |
environments | Restrict the module to crowdin and/or crowdin-enterprise. | 'crowdin' | 'crowdin-enterprise' or array | both |
onRecheck Function
Section titled “onRecheck Function”Receives a single object with these properties:
projectId– Numeric Crowdin project ID.moduleKey– Raw module key (e.g.daily-llm-check). Pass this toreportAdvisorInsight.inspectorId– Fully-prefixedapp:<identifier>:<moduleKey>. Only useful if you need to correlate with Crowdin-side logs.crowdinId– Tenant identifier. Persist this if you’ll callreportAdvisorInsightlater — the value is the only piece you need to bootstrap a token outside of the callback.client– Crowdin API client. May go stale after ~1h; for short-lived work only.context– Context object.
Crowdin may fire multiple nudges in a short window (install + project-create + user-triggered recheck). The SDK does not dedup for you — key your own work on projectId + moduleKey.
reportAdvisorInsight Helper
Section titled “reportAdvisorInsight Helper”Reports the result of an inspector run back to Crowdin. Importable from @crowdin/app-project-module:
const { reportAdvisorInsight } = require('@crowdin/app-project-module');
await reportAdvisorInsight(config, { crowdinId, // from onRecheck args projectId, // from onRecheck args moduleKey, // from onRecheck args (raw key, NOT inspectorId) outcome: 'flagged', // 'flagged' | 'clear' | 'not_applicable' metrics: [ { key: 'demo', value: 1, unit: 'count' } ], recommendations: [ { id: 'demo-rec', primary: true, params: { foo: 'bar' } } ], payload: { foo: 'bar' }, // timeoutMs: 60_000, // optional; default 5 minutes});outcome—'flagged' | 'clear' | 'not_applicable'. Required.metrics— optional, ≤100, unique bykey. Each metric:key(string, required) — stable identifier within the metrics array.value(number, required).unit—'percent' | 'count'.threshold(number) — comparison baseline shown in the UI.tone—'default' | 'success' | 'danger'.checkedAt(RFC3339 string) — per-metric timestamp.
recommendations— optional, ≤20, unique byid. Each:id(string, required) — opaque to Crowdin; resolved by your iframe.primary(boolean).params(object) — string-keyed.
payload— optional. Free-form object or array (PHP-array-shaped — indexed or associative). Crowdin rejects raw scalars.checkedAt(RFC3339 string) — optional top-level timestamp. When omitted, Crowdin setschecked_atto now at upsert time. Supplied values are clamped if more than 1 hour in the future or 7 days in the past.timeoutMs(number) — optional request timeout for the insight PUT. Default300000(5 minutes).
The upsert is full-replace: omitting metrics or recommendations clears those fields on the BE side. The helper sends [] when you omit them, so passing nothing intentionally clears. If you want to preserve previous values, re-send them.