Deployment
You can deploy your Crowdin app to any hosting provider that supports Node.js. Below you can find examples of how to deploy your Crowdin app to Vercel, AWS Lambda, and Cloudflare Workers.
Vercel
Section titled “Vercel”Vercel can deploy your Crowdin app from existing repo (GitHub, Gitlab, Bitbucket).
Vercel Configuration
Section titled “Vercel Configuration”Go to Vercel and proceed with next steps:
- Create a new project.
- Under the
Environment Variablessection, define following variables:CLIENT_ID- Crowdin App Client ID.CLIENT_SECRET- Crowdin App Client Secret.
- Add Postgres SQL Storage.
- Storage -> Connect Store -> Create New -> Select Postgres.
- Deploy.
Example of the Crowdin app
Section titled “Example of the Crowdin app”Project structure:
Directoryroot
- index.js
- logo.png
- package.json
- package-lock.json
- vercel.json
- .gitignore
App configuration:
const crowdinModule = require('@crowdin/app-project-module');
crowdinModule.createApp({ baseUrl: `https://${process.env.VERCEL_URL}`, clientId: process.env.CLIENT_ID, clientSecret: process.env.CLIENT_SECRET, name: 'Sample App', identifier: 'sample-app', description: 'Sample App description', postgreConfig: { host: process.env.POSTGRES_HOST, user: process.env.POSTGRES_USER, password: process.env.POSTGRES_PASSWORD, database: process.env.POSTGRES_DATABASE, ssl: true }, imagePath: __dirname + '/' + 'logo.png', customMT: { translate: () => { console.log('translate'); }, validate: () => { console.log('validate'); } }});{ "version": 2, "builds": [ { "src": "./index.js", "use": "@vercel/node" } ], "routes": [ { "src": "/(.*)", "dest": "/" } ]}/node_modules/{ "name": "crowdin-app-demo", "version": "1.0.0", "description": "", "main": "index.js", "engines": { "node": "14.x" }, "scripts": { "start": "node index.js" }, "author": "", "license": "ISC", "dependencies": { "@crowdin/app-project-module": "^1.0.0" }}AWS Lambda
Section titled “AWS Lambda”You can use Serverless to deploy your Crowdin app to AWS Lambda.
Example of the Crowdin app
Section titled “Example of the Crowdin app”It uses Serverless framework so in order to deploy it to AWS you just need to install deps npm i and after run this command serverless deploy.
In this example we are using PostgreSQL as a storage (e.g. AWS RDS with PostgreSQL engine can be used). Visit the Database page to see other options.
Project structure:
Directoryroot
- handler.js
- logo.svg
- package.json
- env.json
- package-lock.json
- serverless.yml
- .gitignore
const serverless = require('serverless-http');const path = require('path');const express = require('express');const app = express();const crowdinApp = require('@crowdin/app-project-module');
crowdinApp.addCrowdinEndpoints(app, { baseUrl: process.env.LAMBDA_URL, clientId: process.env.CLIENT_ID, clientSecret: process.env.CLIENT_SECRET, name: 'Sample App', identifier: 'sample-app', description: 'Sample App description', postgreConfig: { user: process.env.PG_USER, password: process.env.PG_PASSWORD, database: process.env.PG_DB, host: process.env.PG_HOST }, imagePath: path.join(__dirname, 'logo.svg'), projectIntegration: { getIntegrationFiles: async () => { return [ { id: '12', name: 'File from integration', type: 'json', parentId: '10' }, { id: '14', name: 'File from integration 2', type: 'xml', parentId: '11' }, { id: '10', name: 'Folder from integration' }, { id: '11', name: 'Folder from integration 2' } ]; }, updateCrowdin: async () => { //TODO implement }, updateIntegration: async () => { //TODO implement } }});
module.exports.handler = serverless(app);service: aws-node-express-apiframeworkVersion: '3'
provider: name: aws runtime: nodejs14.x
functions: api: handler: handler.handler environment: ${file(env.json)} url: true{ "LAMBDA_URL": "https://<id>.execute-api.us-east-1.amazonaws.com", "CLIENT_ID": "<client-id>", "CLIENT_SECRET": "<client-secret>", "PG_HOST": "<pg_host>", "PG_USER": "<pg_user>", "PG_PASSWORD": "<pg_pass>", "PG_DB": "<pg_db>"}node_modules.serverless{ "name": "crowdin-serverless", "version": "1.0.0", "description": "", "dependencies": { "express": "^5.0.0", "serverless-http": "^3.0.0", "@crowdin/app-project-module": "^1.0.0" }, "author": "", "license": "ISC"}Cloudflare Workers
Section titled “Cloudflare Workers”Deploy your Crowdin app to Cloudflare Workers with D1 database, KV storage for files, and static assets support.
Prerequisites: Wrangler CLI, Cloudflare account
Setup:
- Create D1 database:
npx wrangler d1 create crowdin-app-db - Create KV namespace:
npx wrangler kv namespace create KVStore - Update
wrangler.jsoncwith the generated IDs
Project Structure
Section titled “Project Structure”Directoryroot
Directoryworker/
- index.ts
- app.ts
Directorypublic/
- logo.svg
- package.json
- wrangler.jsonc
- .gitignore
Configuration Files
Section titled “Configuration Files”{ "$schema": "node_modules/wrangler/config-schema.json", "name": "crowdin-app", "main": "worker/index.ts", "compatibility_date": "2025-01-01", "compatibility_flags": [ "nodejs_compat", "enable_nodejs_http_modules", "enable_nodejs_http_server_modules" ], "assets": { "binding": "ASSETS", "run_worker_first": ["/*", "!/@*", "!/src/*", "!/node_modules/*"] }, "d1_databases": [ { "binding": "DB", "database_name": "crowdin-app-db", "database_id": "your-d1-database-id" } ], "kv_namespaces": [ { "binding": "KVStore", "id": "your-kv-namespace-id" } ], "triggers": { "crons": ["0 * * * *"] }, "vars": { "CROWDIN_CLIENT_ID": "your-client-id", "CROWDIN_CLIENT_SECRET": "your-client-secret" }}import { httpServerHandler } from 'cloudflare:node';import { createApp } from './app';import * as crowdinModule from '@crowdin/app-project-module';import type { Cron } from '@crowdin/app-project-module/out/types';import type { Server } from 'node:http';
// Close previous server on HMR reloadconst previousServer = (globalThis as any).__previousServer as Server | undefined;if (previousServer) { previousServer.close();}
// Module-scoped statelet handler: ExportedHandler | undefined;let appInstance: ReturnType<typeof createApp> | undefined;
class CloudflareCron implements Cron { private handlers: Map<string, Array<() => Promise<void>>> = new Map();
schedule(expression: string, task: () => Promise<void>): void { if (!this.handlers.has(expression)) { this.handlers.set(expression, []); } this.handlers.get(expression)?.push(task); }
async runScheduled(expression: string): Promise<void> { const tasks = this.handlers.get(expression) || []; await Promise.allSettled(tasks.map(task => task())); }}
const cron = new CloudflareCron();
function initializeApp(env: CloudflareEnv) { if (appInstance) { return appInstance; }
const app = crowdinModule.express();
const crowdinApp = createApp({ app, clientId: env.CROWDIN_CLIENT_ID, clientSecret: env.CROWDIN_CLIENT_SECRET, assetsConfig: { fetcher: env.ASSETS, }, d1Config: { database: env.DB, }, fileStore: { getFile: async (fileId: string): Promise<Buffer> => { const data = await env.KVStore.get(fileId, 'arrayBuffer'); if (!data) { throw new Error(`File not found: ${fileId}`); } return Buffer.from(data); }, storeFile: async (content: Buffer): Promise<string> => { const fileId = `file_${crypto.randomUUID()}`; await env.KVStore.put(fileId, content, { expirationTtl: 86400 }); return fileId; }, deleteFile: async (fileId: string): Promise<void> => { await env.KVStore.delete(fileId); } }, cron });
appInstance = crowdinApp; return appInstance;}
function initializeHandler(env: CloudflareEnv): ExportedHandler { if (handler) { return handler; }
const { app } = initializeApp(env); const port: number = 3000; (globalThis as any).__previousServer = app.listen(port); handler = httpServerHandler({ port });
return handler;}
export default { async fetch(request: Request, env: CloudflareEnv, ctx: ExecutionContext) { return initializeHandler(env).fetch!(request, env, ctx); }, async scheduled(controller: ScheduledController, env: CloudflareEnv, ctx: ExecutionContext) { console.log(`Cron triggered: ${controller.cron}`);
try { initializeApp(env); await cron.runScheduled(controller.cron); } catch (error) { console.error('Error executing scheduled tasks:', error); } }};import * as crowdinModule from '@crowdin/app-project-module';import type { AssetsConfig, FileStore, Cron, ClientConfig } from '@crowdin/app-project-module/out/types';import type { D1StorageConfig } from '@crowdin/app-project-module/out/storage/d1';
export function createApp({ app, clientId, clientSecret, assetsConfig, d1Config, fileStore, cron}: { app: ReturnType<typeof crowdinModule.express>; clientId: string; clientSecret: string; assetsConfig: AssetsConfig; d1Config: D1StorageConfig; fileStore: FileStore; cron: Cron;}) { const configuration: ClientConfig = { name: "My Crowdin App", identifier: "my-crowdin-app", description: "Crowdin app deployed on Cloudflare Workers", clientId, clientSecret, disableLogsFormatter: true, assetsConfig, d1Config, fileStore, cron, imagePath: '/logo.svg', assetsPath: '/assets',
// Add your module configurations here // Example: customMT, projectIntegration, etc. };
const crowdinApp = crowdinModule.addCrowdinEndpoints(app, configuration);
return { app, crowdinApp };}{ "name": "crowdin-app", "version": "1.0.0", "type": "module", "scripts": { "dev": "wrangler dev", "deploy": "wrangler deploy", "cf-typegen": "wrangler types --env-interface CloudflareEnv" }, "dependencies": { "@crowdin/app-project-module": "^1.0.0" }, "devDependencies": { "@cloudflare/workers-types": "^4.0.0", "wrangler": "^4.0.0" }}node_modules.wrangler.dev.varsDeploy:
- Install dependencies:
npm install - Generate types:
npm run cf-typegen - Deploy:
npm run deploy
Cron: Configure scheduled events in wrangler.jsonc under triggers.crons using standard cron syntax.
Assets: Static files from the public/ directory are automatically served via Cloudflare’s edge network using the ASSETS binding.