Overview
You can create Crowdin apps that extend the functionality of Crowdin by adding new modules to the Crowdin UI. Modules are displayed in the Crowdin UI and can be used to add new features or modify existing ones.
Supported Modules
Section titled “Supported Modules”Project IntegrationAllows you to create and add a new integration into your Crowdin project.
File ProcessorAllows you to add support for new custom file formats and customize processing for supported file formats.
Other Supported Modules
Section titled “Other Supported Modules”The SDK supports all the other modules that are available in Crowdin:
- profile-resources-menu -
profileResourcesMenu - organization-menu -
organizationMenu - editor-right-panel -
editorRightPanel - project-menu -
projectMenu - project-menu-crowdsource -
projectMenuCrowdsource - project-tools -
projectTools - project-reports -
projectReports
Sample
Section titled “Sample”Example of profile-resources-menu module:
import crowdinModule from '@crowdin/app-project-module';
const configuration = { baseUrl: 'https://123.ngrok.io', clientId: 'clientId', clientSecret: 'clientSecret', name: 'Sample App', identifier: 'sample-app', description: 'Sample App description', dbFolder: import.meta.dirname, imagePath: import.meta.dirname + '/logo.png', profileResourcesMenu: [ { fileName: 'setup.html', uiPath: import.meta.dirname + '/public', environments: 'crowdin-enterprise' } ]};
crowdinModule.createApp(configuration);import crowdinModule from '@crowdin/app-project-module';import type { ClientConfig } from '@crowdin/app-project-module';
const configuration: ClientConfig = { baseUrl: 'https://123.ngrok.io', clientId: 'clientId', clientSecret: 'clientSecret', name: 'Sample App', identifier: 'sample-app', description: 'Sample App description', dbFolder: import.meta.dirname, imagePath: import.meta.dirname + '/logo.png', profileResourcesMenu: [ { fileName: 'setup.html', uiPath: import.meta.dirname + '/public', environments: 'crowdin-enterprise' } ]};
crowdinModule.createApp(configuration);Configuration
Section titled “Configuration”| Parameter | Description | Example |
|---|---|---|
imagePath | Path to the module logo (can be different image than the app logo). | import.meta.dirname + '/reports.png' |
fileName | Optional, only needed if file is not index.html. | 'reports.html' |
uiPath | Folder where UI of the module is located (js, html, css files). | import.meta.dirname + '/public' |
environments | Environment where the module should render. | crowdin / enterprise |
imageUrl | Alternative to imagePath to specify if logo is hosted remotely. | 'https://test.com/reports.png' |
Combining Modules
Section titled “Combining Modules”Each module can work as an extension to other modules, being something like a configuration UI for them:
import crowdinModule from '@crowdin/app-project-module';
const app = crowdinModule.express();
const configuration = { baseUrl: 'https://123.ngrok.io', clientId: 'clientId', clientSecret: 'clientSecret', name: 'Sample App', identifier: 'sample-app', description: 'Sample App description', dbFolder: import.meta.dirname, imagePath: import.meta.dirname + '/logo.png', profileResourcesMenu: [ { fileName: 'setup.html', uiPath: import.meta.dirname + '/public', environments: 'crowdin' } ], customMT: [ { translate } ], onUninstall: cleanup};
const crowdinApp = crowdinModule.addCrowdinEndpoints(app, configuration);const metadataStore = crowdinModule.metadataStore;
async function cleanup({ organization, allCredentials }) { // Cleanup logic await crowdinApp.deleteMetadata(organization);}
async function translate({ client, context, projectId, source, target, strings }) { const organization = context.jwtPayload.domain || context.jwtPayload.context.organization_id; const metadata = await metadataStore.getMetadata(organization); // do translation based on metadata const translations = ['hello', 'world']; return translations;}
// extra endpoints for resources UIapp.post('/metadata', async (req, res) => { // Always specify moduleKey to restrict access to authorized modules const { context } = await crowdinApp.establishCrowdinConnection({ jwtToken: req.query.jwt, moduleKey: ['sample-app-profile-resources'] // Only this module can access }); const id = `${context.jwtPayload.context.organization_id}_${context.jwtPayload.context.project_id}`; const organization = context.jwtPayload.domain || context.jwtPayload.context.organization_id; const metadata = await metadataStore.getMetadata(organization); await crowdinApp.saveMetadata({ id, metadata: req.body, crowdinId: organization }); res.status(204).end();});
app.get('/metadata', async (req, res) => { // Always specify moduleKey to restrict access to authorized modules const { context } = await crowdinApp.establishCrowdinConnection({ jwtToken: req.query.jwt, moduleKey: ['sample-app-profile-resources'] // Only this module can access }); const id = `${context.jwtPayload.context.organization_id}_${context.jwtPayload.context.project_id}`; const metadata = await metadataStore.getMetadata(id) || {}; res.status(200).send(metadata);});
app.listen(3000, () => console.log('Crowdin app started'));import crowdinModule from '@crowdin/app-project-module';import type { ClientConfig, OnUninstallHandler } from '@crowdin/app-project-module';import type { CustomMTTranslateHandler } from '@crowdin/app-project-module/modules/custom-mt';
const app = crowdinModule.express();
const cleanup: OnUninstallHandler = async ({ organization, allCredentials }) => { // Cleanup logic await crowdinApp.deleteMetadata(organization);};
const translate: CustomMTTranslateHandler = async ({ client, context, projectId, source, target, strings }) => { const organization = String(context.jwtPayload.domain || context.jwtPayload.context.organization_id); const metadata = await metadataStore.getMetadata(organization); // do translation based on metadata const translations = ['hello', 'world']; return translations;};
const configuration: ClientConfig = { baseUrl: 'https://123.ngrok.io', clientId: 'clientId', clientSecret: 'clientSecret', name: 'Sample App', identifier: 'sample-app', description: 'Sample App description', dbFolder: import.meta.dirname, imagePath: import.meta.dirname + '/logo.png', profileResourcesMenu: [ { fileName: 'setup.html', uiPath: import.meta.dirname + '/public', environments: 'crowdin' } ], customMT: [ { translate } ], onUninstall: cleanup};
const crowdinApp = crowdinModule.addCrowdinEndpoints(app, configuration);const metadataStore = crowdinModule.metadataStore;
// extra endpoints for resources UIapp.post('/metadata', async (req, res) => { // Always specify moduleKey to restrict access to authorized modules const { context } = await crowdinApp.establishCrowdinConnection({ jwtToken: req.query.jwt as string, moduleKey: ['sample-app-profile-resources'] // Only this module can access }); const id = `${context.jwtPayload.context.organization_id}_${context.jwtPayload.context.project_id}`; const organization = String(context.jwtPayload.domain || context.jwtPayload.context.organization_id); const metadata = await metadataStore.getMetadata(organization); await crowdinApp.saveMetadata({ id, metadata: req.body, crowdinId: organization }); res.status(204).end();});
app.get('/metadata', async (req, res) => { // Always specify moduleKey to restrict access to authorized modules const { context } = await crowdinApp.establishCrowdinConnection({ jwtToken: req.query.jwt as string, moduleKey: ['sample-app-profile-resources'] // Only this module can access }); const id = `${context.jwtPayload.context.organization_id}_${context.jwtPayload.context.project_id}`; const metadata = await metadataStore.getMetadata(id) || {}; res.status(200).send(metadata);});
app.listen(3000, () => console.log('Crowdin app started'));Read more about App Metadata.